From 16c469a671222fda0ab186461566ec05d4698b36 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 5 Dec 2023 14:35:27 -0800 Subject: [PATCH] eng: add a lint rule for ensureNoDisposablesAreLeakedInTestSuite (#200089) Adds a lint rule that ensures ensureNoDisposablesAreLeakedInTestSuite is called in suites. It grandfathers in existing files that were lacking the call entirely. This PR also includes manual fixes to files that used the function already but were missing it in one or more suites, which the lint rule detects. --- ...code-ensure-no-disposables-leak-in-test.ts | 38 ++ .eslintrc.json | 220 ++++++++++ .../base/parts/ipc/test/node/ipc.net.test.ts | 10 +- .../test/browser/ui/list/rangeMap.test.ts | 2 + .../test/browser/ui/tree/objectTree.test.ts | 10 +- src/vs/base/test/common/event.test.ts | 415 +++++++++--------- src/vs/base/test/common/history.test.ts | 2 + src/vs/base/test/common/lifecycle.test.ts | 7 +- .../test/browser/multicursor.test.ts | 2 + .../node/extensionsScannerService.test.ts | 2 + .../test/browser/fileUserDataProvider.test.ts | 40 +- .../common/userDataSyncStoreService.test.ts | 3 + .../test/browser/extHostDocumentData.test.ts | 2 + .../chat/test/common/chatModel.test.ts | 2 + .../test/browser/notebookCommon.test.ts | 5 + .../test/browser/notebookSelection.test.ts | 3 + .../services/label/test/browser/label.test.ts | 2 + 17 files changed, 531 insertions(+), 234 deletions(-) create mode 100644 .eslintplugin/code-ensure-no-disposables-leak-in-test.ts diff --git a/.eslintplugin/code-ensure-no-disposables-leak-in-test.ts b/.eslintplugin/code-ensure-no-disposables-leak-in-test.ts new file mode 100644 index 00000000000..ae3089036a6 --- /dev/null +++ b/.eslintplugin/code-ensure-no-disposables-leak-in-test.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { Node } from 'estree'; + +export = new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + type: 'problem', + messages: { + ensure: 'Suites should include a call to `ensureNoDisposablesAreLeakedInTestSuite()` to ensure no disposables are leaked in tests.' + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + const config = <{ exclude: string[] }>context.options[0]; + + const needle = context.getFilename().replace(/\\/g, '/'); + if (config.exclude.some((e) => needle.endsWith(e))) { + return {}; + } + + return { + [`Program > ExpressionStatement > CallExpression[callee.name='suite']`]: (node: Node) => { + const src = context.getSourceCode().getText(node) + if (!src.includes('ensureNoDisposablesAreLeakedInTestSuite(')) { + context.report({ + node, + messageId: 'ensure', + }); + } + }, + }; + } +}; diff --git a/.eslintrc.json b/.eslintrc.json index fd88130a1e8..d6d14e5d43b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -136,6 +136,226 @@ ] } }, + { + "files": [ + "src/vs/**/*.test.ts" + ], + "rules": { + "local/code-ensure-no-disposables-leak-in-test": [ + "warn", + { + // Files should (only) be removed from the list they adopt the leak detector + "exclude": [ + "src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts", + "src/vs/base/test/browser/browser.test.ts", + "src/vs/base/test/browser/comparers.test.ts", + "src/vs/base/test/browser/dom.test.ts", + "src/vs/base/test/browser/formattedTextRenderer.test.ts", + "src/vs/base/test/browser/hash.test.ts", + "src/vs/base/test/browser/iconLabels.test.ts", + "src/vs/base/test/browser/indexedDB.test.ts", + "src/vs/base/test/browser/ui/contextview/contextview.test.ts", + "src/vs/base/test/browser/ui/menu/menubar.test.ts", + "src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", + "src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", + "src/vs/base/test/common/arrays.test.ts", + "src/vs/base/test/common/arraysFind.test.ts", + "src/vs/base/test/common/assert.test.ts", + "src/vs/base/test/common/cache.test.ts", + "src/vs/base/test/common/charCode.test.ts", + "src/vs/base/test/common/collections.test.ts", + "src/vs/base/test/common/color.test.ts", + "src/vs/base/test/common/console.test.ts", + "src/vs/base/test/common/decorators.test.ts", + "src/vs/base/test/common/diff/diff.test.ts", + "src/vs/base/test/common/errors.test.ts", + "src/vs/base/test/common/filters.perf.test.ts", + "src/vs/base/test/common/filters.test.ts", + "src/vs/base/test/common/iconLabels.test.ts", + "src/vs/base/test/common/iterator.test.ts", + "src/vs/base/test/common/json.test.ts", + "src/vs/base/test/common/jsonEdit.test.ts", + "src/vs/base/test/common/jsonFormatter.test.ts", + "src/vs/base/test/common/keybindings.test.ts", + "src/vs/base/test/common/keyCodes.test.ts", + "src/vs/base/test/common/lazy.test.ts", + "src/vs/base/test/common/linkedList.test.ts", + "src/vs/base/test/common/linkedText.test.ts", + "src/vs/base/test/common/map.test.ts", + "src/vs/base/test/common/markdownString.test.ts", + "src/vs/base/test/common/marshalling.test.ts", + "src/vs/base/test/common/mime.test.ts", + "src/vs/base/test/common/naturalLanguage/korean.test.ts", + "src/vs/base/test/common/network.test.ts", + "src/vs/base/test/common/normalization.test.ts", + "src/vs/base/test/common/objects.test.ts", + "src/vs/base/test/common/observable.test.ts", + "src/vs/base/test/common/path.test.ts", + "src/vs/base/test/common/prefixTree.test.ts", + "src/vs/base/test/common/resources.test.ts", + "src/vs/base/test/common/resourceTree.test.ts", + "src/vs/base/test/common/scrollable.test.ts", + "src/vs/base/test/common/skipList.test.ts", + "src/vs/base/test/common/strings.test.ts", + "src/vs/base/test/common/stripComments.test.ts", + "src/vs/base/test/common/ternarySearchtree.test.ts", + "src/vs/base/test/common/tfIdf.test.ts", + "src/vs/base/test/common/types.test.ts", + "src/vs/base/test/common/uri.test.ts", + "src/vs/base/test/common/uuid.test.ts", + "src/vs/base/test/node/crypto.test.ts", + "src/vs/base/test/node/css.build.test.ts", + "src/vs/base/test/node/id.test.ts", + "src/vs/base/test/node/nodeStreams.test.ts", + "src/vs/base/test/node/port.test.ts", + "src/vs/base/test/node/powershell.test.ts", + "src/vs/base/test/node/snapshot.test.ts", + "src/vs/base/test/node/unc.test.ts", + "src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts", + "src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts", + "src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts", + "src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts", + "src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts", + "src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts", + "src/vs/editor/contrib/folding/test/browser/indentFold.test.ts", + "src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts", + "src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts", + "src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts", + "src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts", + "src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts", + "src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts", + "src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts", + "src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts", + "src/vs/editor/test/common/services/languageService.test.ts", + "src/vs/editor/test/node/classification/typescript.test.ts", + "src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts", + "src/vs/editor/test/node/diffing/fixtures.test.ts", + "src/vs/platform/configuration/test/common/configuration.test.ts", + "src/vs/platform/configuration/test/common/configurationModels.test.ts", + "src/vs/platform/configuration/test/common/configurationRegistry.test.ts", + "src/vs/platform/contextkey/test/common/contextkey.test.ts", + "src/vs/platform/contextkey/test/common/parser.test.ts", + "src/vs/platform/contextkey/test/common/scanner.test.ts", + "src/vs/platform/dialogs/test/common/dialog.test.ts", + "src/vs/platform/environment/test/node/argv.test.ts", + "src/vs/platform/environment/test/node/userDataPath.test.ts", + "src/vs/platform/extensionManagement/test/common/configRemotes.test.ts", + "src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts", + "src/vs/platform/extensions/test/common/extensionValidator.test.ts", + "src/vs/platform/externalTerminal/electron-main/externalTerminalService.test.ts", + "src/vs/platform/instantiation/test/common/graph.test.ts", + "src/vs/platform/instantiation/test/common/instantiationService.test.ts", + "src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts", + "src/vs/platform/keybinding/test/common/keybindingLabels.test.ts", + "src/vs/platform/keybinding/test/common/keybindingResolver.test.ts", + "src/vs/platform/markers/test/common/markerService.test.ts", + "src/vs/platform/opener/test/common/opener.test.ts", + "src/vs/platform/progress/test/common/progress.test.ts", + "src/vs/platform/registry/test/common/platform.test.ts", + "src/vs/platform/remote/test/common/remoteHosts.test.ts", + "src/vs/platform/telemetry/test/browser/1dsAppender.test.ts", + "src/vs/platform/telemetry/test/browser/telemetryService.test.ts", + "src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts", + "src/vs/platform/undoRedo/test/common/undoRedoService.test.ts", + "src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts", + "src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts", + "src/vs/platform/userDataSync/test/common/settingsMerge.test.ts", + "src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts", + "src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts", + "src/vs/platform/workspace/test/common/workspace.test.ts", + "src/vs/platform/workspaces/test/common/workspaces.test.ts", + "src/vs/platform/workspaces/test/electron-main/workspaces.test.ts", + "src/vs/server/test/node/serverConnectionToken.test.ts", + "src/vs/workbench/api/test/browser/extHost.api.impl.test.ts", + "src/vs/workbench/api/test/browser/extHostApiCommands.test.ts", + "src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts", + "src/vs/workbench/api/test/browser/extHostCommands.test.ts", + "src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts", + "src/vs/workbench/api/test/browser/extHostMessagerService.test.ts", + "src/vs/workbench/api/test/browser/extHostTelemetry.test.ts", + "src/vs/workbench/api/test/browser/extHostTextEditor.test.ts", + "src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts", + "src/vs/workbench/api/test/browser/extHostTypes.test.ts", + "src/vs/workbench/api/test/browser/extHostWorkspace.test.ts", + "src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts", + "src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts", + "src/vs/workbench/api/test/common/extensionHostMain.test.ts", + "src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts", + "src/vs/workbench/api/test/node/extHostTunnelService.test.ts", + "src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts", + "src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts", + "src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts", + "src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts", + "src/vs/workbench/contrib/debug/test/browser/callStack.test.ts", + "src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts", + "src/vs/workbench/contrib/debug/test/browser/repl.test.ts", + "src/vs/workbench/contrib/debug/test/common/debugModel.test.ts", + "src/vs/workbench/contrib/debug/test/node/debugger.test.ts", + "src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts", + "src/vs/workbench/contrib/debug/test/node/terminals.test.ts", + "src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts", + "src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts", + "src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts", + "src/vs/workbench/contrib/files/test/browser/explorerFileNestingTrie.test.ts", + "src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts", + "src/vs/workbench/contrib/files/test/browser/explorerView.test.ts", + "src/vs/workbench/contrib/files/test/browser/fileActions.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/cellDnd.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/contrib/executionStatusBarItem.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/contrib/layoutActions.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/contrib/outputCopyTests.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/notebookBrowser.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts", + "src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts", + "src/vs/workbench/contrib/search/test/common/cacheState.test.ts", + "src/vs/workbench/contrib/search/test/common/extractRange.test.ts", + "src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts", + "src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts", + "src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts", + "src/vs/workbench/contrib/tags/test/node/workspaceTags.test.ts", + "src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts", + "src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts", + "src/vs/workbench/contrib/terminal/test/browser/terminalActions.test.ts", + "src/vs/workbench/contrib/themes/test/node/colorRegistryExport.test.ts", + "src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts", + "src/vs/workbench/contrib/welcomeGettingStarted/test/browser/gettingStartedMarkdownRenderer.test.ts", + "src/vs/workbench/services/commands/test/common/commandService.test.ts", + "src/vs/workbench/services/configuration/test/common/configurationModels.test.ts", + "src/vs/workbench/services/configurationResolver/test/electron-sandbox/configurationResolverService.test.ts", + "src/vs/workbench/services/dialogs/test/electron-sandbox/fileDialogService.test.ts", + "src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts", + "src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts", + "src/vs/workbench/services/keybinding/test/browser/keybindingIO.test.ts", + "src/vs/workbench/services/keybinding/test/node/fallbackKeyboardMapper.test.ts", + "src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts", + "src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts", + "src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts", + "src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts", + "src/vs/workbench/services/search/test/browser/queryBuilder.test.ts", + "src/vs/workbench/services/search/test/common/ignoreFile.test.ts", + "src/vs/workbench/services/search/test/common/queryBuilder.test.ts", + "src/vs/workbench/services/search/test/common/replace.test.ts", + "src/vs/workbench/services/search/test/common/search.test.ts", + "src/vs/workbench/services/search/test/common/searchHelpers.test.ts", + "src/vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts", + "src/vs/workbench/services/search/test/node/ripgrepTextSearchEngineUtils.test.ts", + "src/vs/workbench/services/search/test/node/textSearchManager.test.ts", + "src/vs/workbench/services/telemetry/test/browser/commonProperties.test.ts", + "src/vs/workbench/services/textfile/test/common/textFileService.io.test.ts", + "src/vs/workbench/services/textMate/test/browser/arrayOperation.test.ts", + "src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts", + "src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts", + "src/vs/workbench/services/workspaces/test/browser/workspaces.test.ts", + "src/vs/workbench/services/workspaces/test/common/workspaceTrust.test.ts", + "src/vs/workbench/test/browser/quickAccess.test.ts", + "src/vs/workbench/test/browser/webview.test.ts" + ] + } + ] + } + }, { "files": [ "**/vscode.d.ts", diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 8255c780ccf..0e018300cea 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -571,6 +571,8 @@ flakySuite('IPC, create handle', () => { suite('WebSocketNodeSocket', () => { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + function toUint8Array(data: number[]): Uint8Array { const result = new Uint8Array(data.length); for (let i = 0; i < data.length; i++) { @@ -724,15 +726,15 @@ suite('WebSocketNodeSocket', () => { server.close(); const webSocketNodeSocket = new WebSocketNodeSocket(new NodeSocket(socket), true, null, false); - webSocketNodeSocket.onData((data) => { + ds.add(webSocketNodeSocket.onData((data) => { receivingSideOnDataCallCount++; receivingSideTotalBytes += data.byteLength; - }); + })); - webSocketNodeSocket.onClose(() => { + ds.add(webSocketNodeSocket.onClose(() => { webSocketNodeSocket.dispose(); receivingSideSocketClosedBarrier.open(); - }); + })); }); const socket = connect({ diff --git a/src/vs/base/test/browser/ui/list/rangeMap.test.ts b/src/vs/base/test/browser/ui/list/rangeMap.test.ts index 0171250324b..5b3b4a6c65f 100644 --- a/src/vs/base/test/browser/ui/list/rangeMap.test.ts +++ b/src/vs/base/test/browser/ui/list/rangeMap.test.ts @@ -361,6 +361,8 @@ suite('RangeMap', () => { suite('RangeMap with top padding', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('empty', () => { const rangeMap = new RangeMap(10); assert.strictEqual(rangeMap.size, 10); diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts index a3e39846a5b..05594bf561a 100644 --- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -250,12 +250,14 @@ suite('CompressibleObjectTree', function () { disposeTemplate(): void { } } + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + test('empty', function () { const container = document.createElement('div'); container.style.width = '200px'; container.style.height = '200px'; - const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); + const tree = ds.add(new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()])); tree.layout(200); assert.strictEqual(getRowsTextContent(container).length, 0); @@ -266,7 +268,7 @@ suite('CompressibleObjectTree', function () { container.style.width = '200px'; container.style.height = '200px'; - const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); + const tree = ds.add(new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()])); tree.layout(200); tree.setChildren(null, [ @@ -289,7 +291,7 @@ suite('CompressibleObjectTree', function () { container.style.width = '200px'; container.style.height = '200px'; - const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); + const tree = ds.add(new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()])); tree.layout(200); tree.setChildren(null, [ @@ -341,7 +343,7 @@ suite('CompressibleObjectTree', function () { container.style.width = '200px'; container.style.height = '200px'; - const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); + const tree = ds.add(new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()])); tree.layout(200); tree.setChildren(null, [ diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 2231acac008..df7aac4363d 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -40,11 +40,17 @@ namespace Samples { this._onDidChange.fire(value); } + dispose() { + this._onDidChange.dispose(); + } + } } suite('Event utils dispose', function () { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + let tracker = new DisposableTracker(); function assertDisposablesCount(expected: number | Array) { @@ -75,7 +81,7 @@ suite('Event utils dispose', function () { test('no leak with snapshot-utils', function () { const store = new DisposableStore(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const evens = Event.filter(emitter.event, n => n % 2 === 0, store); assertDisposablesCount(1); // snaphot only listen when `evens` is being listened on @@ -91,7 +97,7 @@ suite('Event utils dispose', function () { test('no leak with debounce-util', function () { const store = new DisposableStore(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l) => 0, undefined, undefined, undefined, undefined, store); assertDisposablesCount(1); // debounce only listens when `debounce` is being listened on @@ -109,13 +115,15 @@ suite('Event utils dispose', function () { suite('Event', function () { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + const counter = new Samples.EventCounter(); setup(() => counter.reset()); test('Emitter plain', function () { - const doc = new Samples.Document3(); + const doc = ds.add(new Samples.Document3()); const subscription = doc.onDidChange(counter.onEvent, counter); @@ -133,10 +141,10 @@ suite('Event', function () { const a = (v: string) => calls.push(`a${v}`); const b = (v: string) => calls.push(`b${v}`); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); - emitter.event(a); - emitter.event(b); + ds.add(emitter.event(a)); + ds.add(emitter.event(b)); const s2 = emitter.event(a); emitter.fire('1'); @@ -150,14 +158,14 @@ suite('Event', function () { test('Emitter, dispose listener during emission', () => { for (let keepFirstMod = 1; keepFirstMod < 4; keepFirstMod++) { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const calls: number[] = []; - const disposables = Array.from({ length: 25 }, (_, n) => emitter.event(() => { + const disposables = Array.from({ length: 25 }, (_, n) => ds.add(emitter.event(() => { if (n % keepFirstMod === 0) { disposables[n].dispose(); } calls.push(n); - })); + }))); emitter.fire(); assert.deepStrictEqual(calls, Array.from({ length: 25 }, (_, n) => n)); @@ -165,14 +173,14 @@ suite('Event', function () { }); test('Emitter, dispose emitter during emission', () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const calls: number[] = []; - const disposables = Array.from({ length: 25 }, (_, n) => emitter.event(() => { + const disposables = Array.from({ length: 25 }, (_, n) => ds.add(emitter.event(() => { if (n === 10) { emitter.dispose(); } calls.push(n); - })); + }))); emitter.fire(); disposables.forEach(d => d.dispose()); @@ -181,15 +189,15 @@ suite('Event', function () { test('Emitter, shared delivery queue', () => { const deliveryQueue = createEventDeliveryQueue(); - const emitter1 = new Emitter({ deliveryQueue }); - const emitter2 = new Emitter({ deliveryQueue }); + const emitter1 = ds.add(new Emitter({ deliveryQueue })); + const emitter2 = ds.add(new Emitter({ deliveryQueue })); const calls: string[] = []; - emitter1.event(d => { calls.push(`${d}a`); if (d === 1) { emitter2.fire(2); } }); - emitter1.event(d => { calls.push(`${d}b`); }); + ds.add(emitter1.event(d => { calls.push(`${d}a`); if (d === 1) { emitter2.fire(2); } })); + ds.add(emitter1.event(d => { calls.push(`${d}b`); })); - emitter2.event(d => { calls.push(`${d}c`); emitter1.dispose(); }); - emitter2.event(d => { calls.push(`${d}d`); }); + ds.add(emitter2.event(d => { calls.push(`${d}c`); emitter1.dispose(); })); + ds.add(emitter2.event(d => { calls.push(`${d}d`); })); emitter1.fire(1); @@ -201,13 +209,13 @@ suite('Event', function () { test('Emitter, handles removal during 3', () => { const fn1 = stub(); const fn2 = stub(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); - emitter.event(fn1); + ds.add(emitter.event(fn1)); const h = emitter.event(() => { h.dispose(); }); - emitter.event(fn2); + ds.add(emitter.event(fn2)); emitter.fire('foo'); assert.deepStrictEqual(fn2.args, [['foo']]); @@ -216,9 +224,9 @@ suite('Event', function () { test('Emitter, handles removal during 2', () => { const fn1 = stub(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); - emitter.event(fn1); + ds.add(emitter.event(fn1)); const h = emitter.event(() => { h.dispose(); }); @@ -230,7 +238,7 @@ suite('Event', function () { test('Emitter, bucket', function () { const bucket: IDisposable[] = []; - const doc = new Samples.Document3(); + const doc = ds.add(new Samples.Document3()); const subscription = doc.onDidChange(counter.onEvent, counter, bucket); doc.setText('far'); @@ -251,8 +259,8 @@ suite('Event', function () { test('Emitter, store', function () { - const bucket = new DisposableStore(); - const doc = new Samples.Document3(); + const bucket = ds.add(new DisposableStore()); + const doc = ds.add(new Samples.Document3()); const subscription = doc.onDidChange(counter.onEvent, counter, bucket); doc.setText('far'); @@ -273,16 +281,16 @@ suite('Event', function () { let firstCount = 0; let lastCount = 0; - const a = new Emitter({ + const a = ds.add(new Emitter({ onWillAddFirstListener() { firstCount += 1; }, onDidRemoveLastListener() { lastCount += 1; } - }); + })); assert.strictEqual(firstCount, 0); assert.strictEqual(lastCount, 0); - let subscription1 = a.event(function () { }); - const subscription2 = a.event(function () { }); + let subscription1 = ds.add(a.event(function () { })); + const subscription2 = ds.add(a.event(function () { })); assert.strictEqual(firstCount, 1); assert.strictEqual(lastCount, 0); @@ -294,26 +302,26 @@ suite('Event', function () { assert.strictEqual(firstCount, 1); assert.strictEqual(lastCount, 1); - subscription1 = a.event(function () { }); + subscription1 = ds.add(a.event(function () { })); assert.strictEqual(firstCount, 2); assert.strictEqual(lastCount, 1); }); test('onWillRemoveListener', () => { let count = 0; - const a = new Emitter({ + const a = ds.add(new Emitter({ onWillRemoveListener() { count += 1; } - }); + })); assert.strictEqual(count, 0); - let subscription = a.event(function () { }); + let subscription = ds.add(a.event(function () { })); assert.strictEqual(count, 0); subscription.dispose(); assert.strictEqual(count, 1); - subscription = a.event(function () { }); + subscription = ds.add(a.event(function () { })); assert.strictEqual(count, 1); }); @@ -322,15 +330,15 @@ suite('Event', function () { setUnexpectedErrorHandler(() => null); try { - const a = new Emitter(); + const a = ds.add(new Emitter()); let hit = false; - a.event(function () { + ds.add(a.event(function () { // eslint-disable-next-line no-throw-literal throw 9; - }); - a.event(function () { + })); + ds.add(a.event(function () { hit = true; - }); + })); a.fire(undefined); assert.strictEqual(hit, true); @@ -343,17 +351,17 @@ suite('Event', function () { const allError: any[] = []; - const a = new Emitter({ + const a = ds.add(new Emitter({ onListenerError(e) { allError.push(e); } - }); + })); let hit = false; - a.event(function () { + ds.add(a.event(function () { // eslint-disable-next-line no-throw-literal throw 9; - }); - a.event(function () { + })); + ds.add(a.event(function () { hit = true; - }); + })); a.fire(undefined); assert.strictEqual(hit, true); assert.deepStrictEqual(allError, [9]); @@ -367,7 +375,7 @@ suite('Event', function () { } const context = {}; - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const reg1 = emitter.event(listener, context); const reg2 = emitter.event(listener, context); @@ -395,7 +403,7 @@ suite('Event', function () { } }); - emitter.event(e => { sum = e; }); + ds.add(emitter.event(e => { sum = e; })); const p = Event.toPromise(emitter.event); @@ -433,18 +441,18 @@ suite('Event', function () { }); test('Emitter - In Order Delivery', function () { - const a = new Emitter(); + const a = ds.add(new Emitter()); const listener2Events: string[] = []; - a.event(function listener1(event) { + ds.add(a.event(function listener1(event) { if (event === 'e1') { a.fire('e2'); // assert that all events are delivered at this point assert.deepStrictEqual(listener2Events, ['e1', 'e2']); } - }); - a.event(function listener2(event) { + })); + ds.add(a.event(function listener2(event) { listener2Events.push(event); - }); + })); a.fire('e1'); // assert that all events are delivered in order @@ -452,25 +460,25 @@ suite('Event', function () { }); test('Emitter, - In Order Delivery 3x', function () { - const a = new Emitter(); + const a = ds.add(new Emitter()); const listener2Events: string[] = []; - a.event(function listener1(event) { + ds.add(a.event(function listener1(event) { if (event === 'e2') { a.fire('e3'); // assert that all events are delivered at this point assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']); } - }); - a.event(function listener1(event) { + })); + ds.add(a.event(function listener1(event) { if (event === 'e1') { a.fire('e2'); // assert that all events are delivered at this point assert.deepStrictEqual(listener2Events, ['e1', 'e2', 'e3']); } - }); - a.event(function listener2(event) { + })); + ds.add(a.event(function listener2(event) { listener2Events.push(event); - }); + })); a.fire('e1'); // assert that all events are delivered in order @@ -478,7 +486,7 @@ suite('Event', function () { }); test('Cannot read property \'_actual\' of undefined #142204', function () { - const e = new Emitter(); + const e = ds.add(new Emitter()); const dispo = e.event(() => { }); dispo.dispose.call(undefined); // assert that disposable can be called with this }); @@ -486,6 +494,8 @@ suite('Event', function () { suite('AsyncEmitter', function () { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + test('event has waitUntil-function', async function () { interface E extends IWaitUntil { @@ -495,11 +505,11 @@ suite('AsyncEmitter', function () { const emitter = new AsyncEmitter(); - emitter.event(e => { + ds.add(emitter.event(e => { assert.strictEqual(e.foo, true); assert.strictEqual(e.bar, 1); assert.strictEqual(typeof e.waitUntil, 'function'); - }); + })); emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None); emitter.dispose(); @@ -515,19 +525,19 @@ suite('AsyncEmitter', function () { let globalState = 0; const emitter = new AsyncEmitter(); - emitter.event(e => { + ds.add(emitter.event(e => { e.waitUntil(timeout(10).then(_ => { assert.strictEqual(globalState, 0); globalState += 1; })); - }); + })); - emitter.event(e => { + ds.add(emitter.event(e => { e.waitUntil(timeout(1).then(_ => { assert.strictEqual(globalState, 1); globalState += 1; })); - }); + })); await emitter.fireAsync({ foo: true }, CancellationToken.None); assert.strictEqual(globalState, 2); @@ -545,7 +555,7 @@ suite('AsyncEmitter', function () { const emitter = new AsyncEmitter(); // e1 - emitter.event(e => { + ds.add(emitter.event(e => { e.waitUntil(timeout(10).then(async _ => { if (e.foo === 1) { await emitter.fireAsync({ foo: 2 }, CancellationToken.None); @@ -553,13 +563,13 @@ suite('AsyncEmitter', function () { done = true; } })); - }); + })); // e2 - emitter.event(e => { + ds.add(emitter.event(e => { events.push(e.foo); e.waitUntil(timeout(7)); - }); + })); await emitter.fireAsync({ foo: 1 }, CancellationToken.None); assert.ok(done); @@ -577,16 +587,16 @@ suite('AsyncEmitter', function () { let globalState = 0; const emitter = new AsyncEmitter(); - emitter.event(e => { + ds.add(emitter.event(e => { globalState += 1; e.waitUntil(new Promise((_r, reject) => reject(new Error()))); - }); + })); - emitter.event(e => { + ds.add(emitter.event(e => { globalState += 1; e.waitUntil(timeout(10)); e.waitUntil(timeout(20).then(() => globalState++)); // multiple `waitUntil` are supported and awaited on - }); + })); await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => { assert.strictEqual(globalState, 3); @@ -601,11 +611,13 @@ suite('AsyncEmitter', function () { suite('PausableEmitter', function () { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + test('basic', function () { const data: number[] = []; - const emitter = new PauseableEmitter(); + const emitter = ds.add(new PauseableEmitter()); - emitter.event(e => data.push(e)); + ds.add(emitter.event(e => data.push(e))); emitter.fire(1); emitter.fire(2); @@ -614,9 +626,9 @@ suite('PausableEmitter', function () { test('pause/resume - no merge', function () { const data: number[] = []; - const emitter = new PauseableEmitter(); + const emitter = ds.add(new PauseableEmitter()); - emitter.event(e => data.push(e)); + ds.add(emitter.event(e => data.push(e))); emitter.fire(1); emitter.fire(2); assert.deepStrictEqual(data, [1, 2]); @@ -634,9 +646,9 @@ suite('PausableEmitter', function () { test('pause/resume - merge', function () { const data: number[] = []; - const emitter = new PauseableEmitter({ merge: (a) => a.reduce((p, c) => p + c, 0) }); + const emitter = ds.add(new PauseableEmitter({ merge: (a) => a.reduce((p, c) => p + c, 0) })); - emitter.event(e => data.push(e)); + ds.add(emitter.event(e => data.push(e))); emitter.fire(1); emitter.fire(2); assert.deepStrictEqual(data, [1, 2]); @@ -655,9 +667,9 @@ suite('PausableEmitter', function () { test('double pause/resume', function () { const data: number[] = []; - const emitter = new PauseableEmitter(); + const emitter = ds.add(new PauseableEmitter()); - emitter.event(e => data.push(e)); + ds.add(emitter.event(e => data.push(e))); emitter.fire(1); emitter.fire(2); assert.deepStrictEqual(data, [1, 2]); @@ -680,9 +692,9 @@ suite('PausableEmitter', function () { test('resume, no pause', function () { const data: number[] = []; - const emitter = new PauseableEmitter(); + const emitter = ds.add(new PauseableEmitter()); - emitter.event(e => data.push(e)); + ds.add(emitter.event(e => data.push(e))); emitter.fire(1); emitter.fire(2); assert.deepStrictEqual(data, [1, 2]); @@ -694,20 +706,20 @@ suite('PausableEmitter', function () { test('nested pause', function () { const data: number[] = []; - const emitter = new PauseableEmitter(); + const emitter = ds.add(new PauseableEmitter()); let once = true; - emitter.event(e => { + ds.add(emitter.event(e => { data.push(e); if (once) { emitter.pause(); once = false; } - }); - emitter.event(e => { + })); + ds.add(emitter.event(e => { data.push(e); - }); + })); emitter.pause(); emitter.fire(1); @@ -727,8 +739,8 @@ suite('PausableEmitter', function () { test('empty pause with merge', function () { const data: number[] = []; - const emitter = new PauseableEmitter({ merge: a => a[0] }); - emitter.event(e => data.push(1)); + const emitter = ds.add(new PauseableEmitter({ merge: a => a[0] })); + ds.add(emitter.event(e => data.push(1))); emitter.pause(); emitter.resume(); @@ -766,12 +778,14 @@ suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () { suite('Event utils', () => { + const ds = ensureNoDisposablesAreLeakedInTestSuite(); + suite('EventBufferer', () => { test('should not buffer when not wrapped', () => { const bufferer = new EventBufferer(); const counter = new Samples.EventCounter(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = bufferer.wrapEvent(emitter.event); const listener = event(counter.onEvent, counter); @@ -789,7 +803,7 @@ suite('Event utils', () => { test('should buffer when wrapped', () => { const bufferer = new EventBufferer(); const counter = new Samples.EventCounter(); - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = bufferer.wrapEvent(emitter.event); const listener = event(counter.onEvent, counter); @@ -812,7 +826,7 @@ suite('Event utils', () => { }); test('once', () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); let counter1 = 0, counter2 = 0, counter3 = 0; @@ -844,7 +858,7 @@ suite('Event utils', () => { test('should buffer events', () => { const result: number[] = []; - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = emitter.event; const bufferedEvent = Event.buffer(event); @@ -866,7 +880,7 @@ suite('Event utils', () => { test('should buffer events on next tick', async () => { const result: number[] = []; - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = emitter.event; const bufferedEvent = Event.buffer(event, true); @@ -888,7 +902,7 @@ suite('Event utils', () => { test('should fire initial buffer events', () => { const result: number[] = []; - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = emitter.event; const bufferedEvent = Event.buffer(event, false, [-2, -1, 0]); @@ -897,7 +911,7 @@ suite('Event utils', () => { emitter.fire(3); assert.deepStrictEqual(result, [] as number[]); - bufferedEvent(num => result.push(num)); + ds.add(bufferedEvent(num => result.push(num))); assert.deepStrictEqual(result, [-2, -1, 0, 1, 2, 3]); }); }); @@ -907,10 +921,10 @@ suite('Event utils', () => { test('works', () => { const result: number[] = []; const m = new EventMultiplexer(); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); - const e1 = new Emitter(); - m.add(e1.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); assert.deepStrictEqual(result, []); @@ -921,10 +935,10 @@ suite('Event utils', () => { test('multiplexer dispose works', () => { const result: number[] = []; const m = new EventMultiplexer(); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); - const e1 = new Emitter(); - m.add(e1.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); assert.deepStrictEqual(result, []); @@ -941,10 +955,10 @@ suite('Event utils', () => { test('event dispose works', () => { const result: number[] = []; const m = new EventMultiplexer(); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); - const e1 = new Emitter(); - m.add(e1.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); assert.deepStrictEqual(result, []); @@ -961,9 +975,9 @@ suite('Event utils', () => { test('mutliplexer event dispose works', () => { const result: number[] = []; const m = new EventMultiplexer(); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); - const e1 = new Emitter(); + const e1 = ds.add(new Emitter()); const l1 = m.add(e1.event); assert.deepStrictEqual(result, []); @@ -981,14 +995,14 @@ suite('Event utils', () => { test('hot start works', () => { const result: number[] = []; const m = new EventMultiplexer(); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); - const e1 = new Emitter(); - m.add(e1.event); - const e2 = new Emitter(); - m.add(e2.event); - const e3 = new Emitter(); - m.add(e3.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); + const e2 = ds.add(new Emitter()); + ds.add(m.add(e2.event)); + const e3 = ds.add(new Emitter()); + ds.add(m.add(e3.event)); e1.fire(1); e2.fire(2); @@ -1000,14 +1014,14 @@ suite('Event utils', () => { const result: number[] = []; const m = new EventMultiplexer(); - const e1 = new Emitter(); - m.add(e1.event); - const e2 = new Emitter(); - m.add(e2.event); - const e3 = new Emitter(); - m.add(e3.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); + const e2 = ds.add(new Emitter()); + ds.add(m.add(e2.event)); + const e3 = ds.add(new Emitter()); + ds.add(m.add(e3.event)); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); e1.fire(1); e2.fire(2); @@ -1019,18 +1033,18 @@ suite('Event utils', () => { const result: number[] = []; const m = new EventMultiplexer(); - const e1 = new Emitter(); - m.add(e1.event); - const e2 = new Emitter(); - m.add(e2.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); + const e2 = ds.add(new Emitter()); + ds.add(m.add(e2.event)); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); e1.fire(1); e2.fire(2); - const e3 = new Emitter(); - m.add(e3.event); + const e3 = ds.add(new Emitter()); + ds.add(m.add(e3.event)); e3.fire(3); assert.deepStrictEqual(result, [1, 2, 3]); @@ -1040,17 +1054,17 @@ suite('Event utils', () => { const result: number[] = []; const m = new EventMultiplexer(); - const e1 = new Emitter(); - m.add(e1.event); - const e2 = new Emitter(); - m.add(e2.event); + const e1 = ds.add(new Emitter()); + ds.add(m.add(e1.event)); + const e2 = ds.add(new Emitter()); + ds.add(m.add(e2.event)); - m.event(r => result.push(r)); + ds.add(m.event(r => result.push(r))); e1.fire(1); e2.fire(2); - const e3 = new Emitter(); + const e3 = ds.add(new Emitter()); const l3 = m.add(e3.event); e3.fire(3); assert.deepStrictEqual(result, [1, 2, 3]); @@ -1066,22 +1080,24 @@ suite('Event utils', () => { }); suite('DynamicListEventMultiplexer', () => { + let addEmitter: Emitter; + let removeEmitter: Emitter; const recordedEvents: number[] = []; - const addEmitter = new Emitter(); - const removeEmitter = new Emitter(); class TestItem { - readonly onTestEventEmitter = new Emitter(); + readonly onTestEventEmitter = ds.add(new Emitter()); readonly onTestEvent = this.onTestEventEmitter.event; } let items: TestItem[]; let m: DynamicListEventMultiplexer; setup(() => { + addEmitter = ds.add(new Emitter()); + removeEmitter = ds.add(new Emitter()); items = [new TestItem(), new TestItem()]; for (const [i, item] of items.entries()) { - item.onTestEvent(e => `${i}:${e}`); + ds.add(item.onTestEvent(e => `${i}:${e}`)); } m = new DynamicListEventMultiplexer(items, addEmitter.event, removeEmitter.event, e => e.onTestEvent); - m.event(e => recordedEvents.push(e)); + ds.add(m.event(e => recordedEvents.push(e))); recordedEvents.length = 0; }); teardown(() => m.dispose()); @@ -1112,11 +1128,11 @@ suite('Event utils', () => { }); test('latch', () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const event = Event.latch(emitter.event); const result: number[] = []; - const listener = event(num => result.push(num)); + const listener = ds.add(event(num => result.push(num))); assert.deepStrictEqual(result, []); @@ -1148,11 +1164,11 @@ suite('Event utils', () => { }); test('dispose is reentrant', () => { - const emitter = new Emitter({ + const emitter = ds.add(new Emitter({ onDidRemoveLastListener: () => { emitter.dispose(); } - }); + })); const listener = emitter.event(() => undefined); listener.dispose(); // should not crash @@ -1164,17 +1180,17 @@ suite('Event utils', () => { return new Promise(resolve => { let promise = new DeferredPromise(); - Event.fromPromise(promise.p)(e => { + ds.add(Event.fromPromise(promise.p)(e => { assert.strictEqual(e, 1); promise = new DeferredPromise(); - Event.fromPromise(promise.p)(() => { + ds.add(Event.fromPromise(promise.p)(() => { resolve(); - }); + })); promise.error(undefined); - }); + })); promise.complete(1); }); @@ -1185,16 +1201,16 @@ suite('Event utils', () => { let promise = new DeferredPromise(); promise.complete(1); - Event.fromPromise(promise.p)(e => { + ds.add(Event.fromPromise(promise.p)(e => { assert.strictEqual(e, 1); promise = new DeferredPromise(); promise.error(undefined); - Event.fromPromise(promise.p)(() => { + ds.add(Event.fromPromise(promise.p)(() => { resolve(); - }); - }); + })); + })); }); }); @@ -1202,8 +1218,8 @@ suite('Event utils', () => { suite('Relay', () => { test('should input work', () => { - const e1 = new Emitter(); - const e2 = new Emitter(); + const e1 = ds.add(new Emitter()); + const e2 = ds.add(new Emitter()); const relay = new Relay(); const result: number[] = []; @@ -1229,13 +1245,13 @@ suite('Event utils', () => { }); test('should Relay dispose work', () => { - const e1 = new Emitter(); - const e2 = new Emitter(); + const e1 = ds.add(new Emitter()); + const e2 = ds.add(new Emitter()); const relay = new Relay(); const result: number[] = []; const listener = (num: number) => result.push(num); - relay.event(listener); + ds.add(relay.event(listener)); e1.fire(1); assert.deepStrictEqual(result, []); @@ -1257,7 +1273,7 @@ suite('Event utils', () => { }); test('runAndSubscribeWithStore', () => { - const eventEmitter = new Emitter(); + const eventEmitter = ds.add(new Emitter()); const event = eventEmitter.event; let i = 0; @@ -1288,14 +1304,14 @@ suite('Event utils', () => { suite('accumulate', () => { test('should not fire after a listener is disposed with undefined or []', async () => { - const eventEmitter = new Emitter(); + const eventEmitter = ds.add(new Emitter()); const event = eventEmitter.event; const accumulated = Event.accumulate(event, 0); const calls1: number[][] = []; const calls2: number[][] = []; - const listener1 = accumulated((e) => calls1.push(e)); - accumulated((e) => calls2.push(e)); + const listener1 = ds.add(accumulated((e) => calls1.push(e))); + ds.add(accumulated((e) => calls2.push(e))); eventEmitter.fire(1); await timeout(1); @@ -1308,29 +1324,29 @@ suite('Event utils', () => { assert.deepStrictEqual(calls2, [[1]], 'should not fire after a listener is disposed with undefined or []'); }); test('should accumulate a single event', async () => { - const eventEmitter = new Emitter(); + const eventEmitter = ds.add(new Emitter()); const event = eventEmitter.event; const accumulated = Event.accumulate(event, 0); const results1 = await new Promise(r => { - accumulated(r); + ds.add(accumulated(r)); eventEmitter.fire(1); }); assert.deepStrictEqual(results1, [1]); const results2 = await new Promise(r => { - accumulated(r); + ds.add(accumulated(r)); eventEmitter.fire(2); }); assert.deepStrictEqual(results2, [2]); }); test('should accumulate multiple events', async () => { - const eventEmitter = new Emitter(); + const eventEmitter = ds.add(new Emitter()); const event = eventEmitter.event; const accumulated = Event.accumulate(event, 0); const results1 = await new Promise(r => { - accumulated(r); + ds.add(accumulated(r)); eventEmitter.fire(1); eventEmitter.fire(2); eventEmitter.fire(3); @@ -1338,7 +1354,7 @@ suite('Event utils', () => { assert.deepStrictEqual(results1, [1, 2, 3]); const results2 = await new Promise(r => { - accumulated(r); + ds.add(accumulated(r)); eventEmitter.fire(4); eventEmitter.fire(5); eventEmitter.fire(6); @@ -1351,7 +1367,7 @@ suite('Event utils', () => { suite('debounce', () => { test('simple', function (done: () => void) { - const doc = new Samples.Document3(); + const doc = ds.add(new Samples.Document3()); const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => { if (!prev) { @@ -1364,7 +1380,7 @@ suite('Event utils', () => { let count = 0; - onDocDidChange(keys => { + ds.add(onDocDidChange(keys => { count++; assert.ok(keys, 'was not expecting keys.'); if (count === 1) { @@ -1374,7 +1390,7 @@ suite('Event utils', () => { assert.deepStrictEqual(keys, ['4']); done(); } - }); + })); doc.setText('1'); doc.setText('2'); @@ -1383,7 +1399,7 @@ suite('Event utils', () => { test('microtask', function (done: () => void) { - const doc = new Samples.Document3(); + const doc = ds.add(new Samples.Document3()); const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => { if (!prev) { @@ -1396,7 +1412,7 @@ suite('Event utils', () => { let count = 0; - onDocDidChange(keys => { + ds.add(onDocDidChange(keys => { count++; assert.ok(keys, 'was not expecting keys.'); if (count === 1) { @@ -1406,7 +1422,7 @@ suite('Event utils', () => { assert.deepStrictEqual(keys, ['4']); done(); } - }); + })); doc.setText('1'); doc.setText('2'); @@ -1415,13 +1431,13 @@ suite('Event utils', () => { test('leading', async function () { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); let calls = 0; - debounced(() => { + ds.add(debounced(() => { calls++; - }); + })); // If the source event is fired once, the debounced (on the leading edge) event should be fired only once emitter.fire(); @@ -1431,13 +1447,13 @@ suite('Event utils', () => { }); test('leading (2)', async function () { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); let calls = 0; - debounced(() => { + ds.add(debounced(() => { calls++; - }); + })); // If the source event is fired multiple times, the debounced (on the leading edge) event should be fired twice emitter.fire(); @@ -1448,11 +1464,11 @@ suite('Event utils', () => { }); test('leading reset', async function () { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true); const calls: number[] = []; - debounced((e) => calls.push(e)); + ds.add(debounced((e) => calls.push(e))); emitter.fire(1); emitter.fire(1); @@ -1462,11 +1478,11 @@ suite('Event utils', () => { }); test('should not flush events when a listener is disposed', async () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0); const calls: number[] = []; - const listener = debounced((e) => calls.push(e)); + const listener = ds.add(debounced((e) => calls.push(e))); emitter.fire(1); listener.dispose(); @@ -1478,11 +1494,11 @@ suite('Event utils', () => { }); test('flushOnListenerRemove - should flush events when a listener is disposed', async () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, undefined, true); const calls: number[] = []; - const listener = debounced((e) => calls.push(e)); + const listener = ds.add(debounced((e) => calls.push(e))); emitter.fire(1); listener.dispose(); @@ -1494,11 +1510,11 @@ suite('Event utils', () => { }); test('should flush events when the emitter is disposed', async () => { - const emitter = new Emitter(); + const emitter = ds.add(new Emitter()); const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0); const calls: number[] = []; - debounced((e) => calls.push(e)); + ds.add(debounced((e) => calls.push(e))); emitter.fire(1); emitter.dispose(); @@ -1509,26 +1525,17 @@ suite('Event utils', () => { }); suite('chain2', () => { - let store: DisposableStore; let em: Emitter; let calls: number[]; - teardown(() => { - store.dispose(); - }); - - ensureNoDisposablesAreLeakedInTestSuite(); - setup(() => { - store = new DisposableStore(); - em = new Emitter(); - store.add(em); + em = ds.add(new Emitter()); calls = []; }); test('maps', () => { const ev = Event.chain(em.event, $ => $.map(v => v * 2)); - store.add(ev(v => calls.push(v))); + ds.add(ev(v => calls.push(v))); em.fire(1); em.fire(2); em.fire(3); @@ -1537,7 +1544,7 @@ suite('Event utils', () => { test('filters', () => { const ev = Event.chain(em.event, $ => $.filter(v => v % 2 === 0)); - store.add(ev(v => calls.push(v))); + ds.add(ev(v => calls.push(v))); em.fire(1); em.fire(2); em.fire(3); @@ -1547,7 +1554,7 @@ suite('Event utils', () => { test('reduces', () => { const ev = Event.chain(em.event, $ => $.reduce((acc, v) => acc + v, 0)); - store.add(ev(v => calls.push(v))); + ds.add(ev(v => calls.push(v))); em.fire(1); em.fire(2); em.fire(3); @@ -1557,7 +1564,7 @@ suite('Event utils', () => { test('latches', () => { const ev = Event.chain(em.event, $ => $.latch()); - store.add(ev(v => calls.push(v))); + ds.add(ev(v => calls.push(v))); em.fire(1); em.fire(1); em.fire(2); @@ -1576,7 +1583,7 @@ suite('Event utils', () => { .latch() ); - store.add(ev(v => calls.push(v))); + ds.add(ev(v => calls.push(v))); em.fire(1); em.fire(2); em.fire(3); diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index eae79bb5afa..39143609b88 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -184,6 +184,8 @@ suite('History Navigator', () => { suite('History Navigator 2', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('constructor', () => { const testObject = new HistoryNavigator2(['1', '2', '3', '4']); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 22e8ff77e0f..103009b5219 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -13,8 +13,9 @@ class Disposable implements IDisposable { dispose() { this.isDisposed = true; } } +// Leaks are allowed here since we test lifecycle stuff: +// eslint-disable-next-line local/code-ensure-no-disposables-leak-in-test suite('Lifecycle', () => { - test('dispose single disposable', () => { const disposable = new Disposable(); @@ -129,6 +130,8 @@ suite('Lifecycle', () => { }); suite('DisposableStore', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('dispose should call all child disposes even if a child throws on dispose', () => { const disposedValues = new Set(); @@ -221,6 +224,8 @@ suite('DisposableStore', () => { }); suite('Reference Collection', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + class Collection extends ReferenceCollection { private _count = 0; get count() { return this._count; } diff --git a/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts index 9d36f7ebfd8..64de23fce27 100644 --- a/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts @@ -83,6 +83,8 @@ function fromRange(rng: Range): number[] { } suite('Multicursor selection', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const serviceCollection = new ServiceCollection(); serviceCollection.set(IStorageService, new InMemoryStorageService()); diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 8994ead0bf4..9e72c1c174a 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -348,6 +348,8 @@ suite('NativeExtensionsScanerService Test', () => { suite('ExtensionScannerInput', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('compare inputs - location', () => { const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, true, '1.1.1', undefined, undefined, true, undefined, {}); diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index ef65f7c57e5..eeeb2486c46 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -4,25 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; -import { dirname, isEqual, joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/product/common/product'; -import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { dirname, isEqual, joinPath } from 'vs/base/common/resources'; import { ReadableStreamEvents } from 'vs/base/common/stream'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { FileChangeType, FileSystemProviderCapabilities, FileType, IFileChange, IFileOpenOptions, IFileReadStreamOptions, IFileService, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat } from 'vs/platform/files/common/files'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { NullLogService } from 'vs/platform/log/common/log'; +import product from 'vs/platform/product/common/product'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -315,7 +315,7 @@ class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapa suite('FileUserDataProvider - Watching', () => { let testObject: FileUserDataProvider; - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const rootFileResource = joinPath(ROOT, 'User'); const rootUserDataResource = rootFileResource.with({ scheme: Schemas.vscodeUserData }); @@ -332,8 +332,6 @@ suite('FileUserDataProvider - Watching', () => { testObject = disposables.add(new FileUserDataProvider(rootFileResource.scheme, new TestFileSystemProvider(fileEventEmitter.event), Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService())); }); - teardown(() => disposables.clear()); - test('file added change event', done => { disposables.add(testObject.watch(rootUserDataResource, { excludes: [], recursive: false })); const expected = joinPath(rootUserDataResource, 'settings.json'); @@ -427,7 +425,7 @@ suite('FileUserDataProvider - Watching', () => { test('event is not triggered if not watched', async () => { const target = joinPath(rootFileResource, 'settings.json'); let triggered = false; - testObject.onDidChangeFile(() => triggered = true); + disposables.add(testObject.onDidChangeFile(() => triggered = true)); fileEventEmitter.fire([{ resource: target, type: FileChangeType.DELETED @@ -441,7 +439,7 @@ suite('FileUserDataProvider - Watching', () => { disposables.add(testObject.watch(rootUserDataResource, { excludes: [], recursive: false })); const target = joinPath(dirname(rootFileResource), 'settings.json'); let triggered = false; - testObject.onDidChangeFile(() => triggered = true); + disposables.add(testObject.onDidChangeFile(() => triggered = true)); fileEventEmitter.fire([{ resource: target, type: FileChangeType.DELETED diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index bc90fb28b24..db83d62e163 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -417,6 +417,9 @@ suite('UserDataSyncRequestsSession', () => { async loadCertificates() { return []; } }; + + ensureNoDisposablesAreLeakedInTestSuite(); + test('too many requests are thrown when limit exceeded', async () => { const testObject = new RequestsSession(1, 500, requestService, new NullLogService()); await testObject.request('url', {}, CancellationToken.None); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index b8912834f80..3f60f5ef898 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -441,6 +441,8 @@ suite('ExtHostDocumentData updates line mapping', () => { testLineMappingDirectionAfterEvents(lines, '\r\n', AssertDocumentLineMappingDirection.OffsetToPosition, e); } + ensureNoDisposablesAreLeakedInTestSuite(); + test('line mapping', () => { testLineMappingAfterEvents([ 'This is line one', diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 33f0e1d0a06..60197332945 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -122,6 +122,8 @@ suite('ChatModel', () => { }); suite('Response', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('content, markdown', async () => { const response = new Response([]); response.updateContent({ content: 'text', kind: 'content' }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 83d0164b035..476c7aa5327 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -305,6 +305,8 @@ suite('NotebookCommon', () => { suite('CellUri', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('parse, generate (file-scheme)', function () { const nb = URI.parse('file:///bar/følder/file.nb'); @@ -348,6 +350,8 @@ suite('CellUri', function () { suite('CellRange', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Cell range to index', function () { assert.deepStrictEqual(cellRangesToIndexes([]), []); assert.deepStrictEqual(cellRangesToIndexes([{ start: 0, end: 0 }]), []); @@ -398,6 +402,7 @@ suite('CellRange', function () { }); suite('NotebookWorkingCopyTypeIdentifier', function () { + ensureNoDisposablesAreLeakedInTestSuite(); test('works', function () { const viewType = 'testViewType'; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index b1aa61118fd..a45fd46828f 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -15,12 +15,15 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { DisposableStore } from 'vs/base/common/lifecycle'; suite('NotebookSelection', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('focus is never empty', function () { const selectionCollection = new NotebookCellSelectionCollection(); assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 }); selectionCollection.setState(null, [], true, 'model'); assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 }); + selectionCollection.dispose(); }); }); diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 520ed89e35d..f5098c5df72 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -28,6 +28,8 @@ suite('URI Label', () => { labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestPathService(URI.file('/foobar')), new TestRemoteAgentService(), storageService, new TestLifecycleService()); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('custom scheme', function () { labelService.registerFormatter({ scheme: 'vscode',