From f071564d8607b4c4776bd8a9573345fa2c0878a5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 5 Dec 2023 13:48:22 -0800 Subject: [PATCH] eng: add a lint rule for ensureNoDisposablesAreLeakedInTestSuite 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 | 36 +- .../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, 529 insertions(+), 232 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 0000000000000..ae3089036a6cf --- /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 fd88130a1e855..d6d14e5d43ba0 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 8255c780ccf5b..0e018300cea33 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 0171250324b46..5b3b4a6c65fe8 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 a3e39846a5b82..05594bf561ae3 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 2231acac008a6..df7aac4363d75 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 eae79bb5afa59..39143609b8801 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 22e8ff77e0f69..103009b521937 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 9d36f7ebfd849..64de23fce27fa 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 8994ead0bf4a4..9e72c1c174ab6 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 ef65f7c57e5d2..eeeb2486c46b3 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 { CancellationToken } from 'vs/base/common/cancellation'; 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 { 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 { 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 { ReadableStreamEvents } from 'vs/base/common/stream'; +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 bc90fb28b24f4..db83d62e16330 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 b8912834f80a8..3f60f5ef89885 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 33f0e1d0a0622..601973329451f 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 83d0164b03524..476c7aa53274d 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 b1aa61118fdf3..a45fd46828f42 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 520ed89e35da9..f5098c5df7250 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',