Skip to content

Commit 9088548

Browse files
authored
debt: finish adopting ensureNoDisposablesAreLeakedInTestSuite (microsoft#249678)
Closes microsoft#200091 Notably makes `raceCancellablePromises` itself be cancellable to allow for composition, and make `Event.toPromise` cancellable too.
1 parent 446a4ab commit 9088548

File tree

15 files changed

+107
-80
lines changed

15 files changed

+107
-80
lines changed

eslint.config.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,7 @@ export default tseslint.config(
223223
{
224224
// Files should (only) be removed from the list they adopt the leak detector
225225
'exclude': [
226-
'src/vs/platform/configuration/test/common/configuration.test.ts',
227-
'src/vs/platform/opener/test/common/opener.test.ts',
228-
'src/vs/platform/registry/test/common/platform.test.ts',
229-
'src/vs/platform/workspace/test/common/workspace.test.ts',
230-
'src/vs/platform/workspaces/test/electron-main/workspaces.test.ts',
231-
'src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts',
232-
'src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts',
233-
'src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts',
234-
'src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts',
235-
'src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts',
236-
'src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts',
237-
'src/vs/workbench/services/commands/test/common/commandService.test.ts',
238226
'src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts',
239-
'src/vs/workbench/test/browser/quickAccess.test.ts'
240227
]
241228
}
242229
]

src/vs/base/common/async.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,19 +119,21 @@ export function raceCancellationError<T>(promise: Promise<T>, token: Cancellatio
119119
/**
120120
* Returns as soon as one of the promises resolves or rejects and cancels remaining promises
121121
*/
122-
export async function raceCancellablePromises<T>(cancellablePromises: CancelablePromise<T>[]): Promise<T> {
122+
export function raceCancellablePromises<T>(cancellablePromises: (CancelablePromise<T> | Promise<T>)[]): CancelablePromise<T> {
123123
let resolvedPromiseIndex = -1;
124124
const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; }));
125-
try {
126-
const result = await Promise.race(promises);
127-
return result;
128-
} finally {
125+
const promise = Promise.race(promises) as CancelablePromise<T>;
126+
promise.cancel = () => {
129127
cancellablePromises.forEach((cancellablePromise, index) => {
130-
if (index !== resolvedPromiseIndex) {
131-
cancellablePromise.cancel();
128+
if (index !== resolvedPromiseIndex && (cancellablePromise as CancelablePromise<T>).cancel) {
129+
(cancellablePromise as CancelablePromise<T>).cancel();
132130
}
133131
});
134-
}
132+
};
133+
promise.finally(() => {
134+
promise.cancel();
135+
});
136+
return promise;
135137
}
136138

137139
export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {

src/vs/base/common/event.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { CancelablePromise } from './async.js';
67
import { CancellationToken } from './cancellation.js';
78
import { diffSets } from './collections.js';
89
import { onUnexpectedError } from './errors.js';
@@ -598,8 +599,16 @@ export namespace Event {
598599
/**
599600
* Creates a promise out of an event, using the {@link Event.once} helper.
600601
*/
601-
export function toPromise<T>(event: Event<T>, disposables?: IDisposable[] | DisposableStore): Promise<T> {
602-
return new Promise(resolve => once(event)(resolve, null, disposables));
602+
export function toPromise<T>(event: Event<T>, disposables?: IDisposable[] | DisposableStore): CancelablePromise<T> {
603+
let cancelRef: () => void;
604+
const promise = new Promise((resolve, reject) => {
605+
const listener = once(event)(resolve, null, disposables);
606+
// not resolved, matching the behavior of a normal disposal
607+
cancelRef = () => listener.dispose();
608+
}) as CancelablePromise<T>;
609+
promise.cancel = cancelRef!;
610+
611+
return promise;
603612
}
604613

605614
/**

src/vs/platform/configuration/test/common/configuration.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import assert from 'assert';
66
import { merge, removeFromValueTree } from '../../common/configuration.js';
77
import { mergeChanges } from '../../common/configurationModels.js';
8+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
89

910
suite('Configuration', () => {
1011

12+
ensureNoDisposablesAreLeakedInTestSuite();
13+
1114
test('simple merge', () => {
1215
let base = { 'a': 1, 'b': 2 };
1316
merge(base, { 'a': 3, 'c': 4 }, true);
@@ -121,6 +124,8 @@ suite('Configuration', () => {
121124

122125
suite('Configuration Changes: Merge', () => {
123126

127+
ensureNoDisposablesAreLeakedInTestSuite();
128+
124129
test('merge only keys', () => {
125130
const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] });
126131
assert.deepStrictEqual(actual, { keys: ['a', 'b', 'c', 'd'], overrides: [] });

src/vs/platform/opener/test/common/opener.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import assert from 'assert';
77
import { URI } from '../../../../base/common/uri.js';
88
import { extractSelection, withSelection } from '../../common/opener.js';
9+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
910

1011
suite('extractSelection', () => {
1112

13+
ensureNoDisposablesAreLeakedInTestSuite();
14+
1215
test('extractSelection with only startLineNumber', async () => {
1316
const uri = URI.parse('file:///some/file.js#73');
1417
assert.deepStrictEqual(extractSelection(uri).selection, { startLineNumber: 73, startColumn: 1, endLineNumber: undefined, endColumn: undefined });

src/vs/platform/quickinput/browser/quickAccess.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { DeferredPromise } from '../../../base/common/async.js';
77
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
88
import { Event } from '../../../base/common/event.js';
9-
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
9+
import { Disposable, DisposableStore, IDisposable, isDisposable, toDisposable } from '../../../base/common/lifecycle.js';
1010
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
1111
import { DefaultQuickAccessFilterValue, Extensions, IQuickAccessController, IQuickAccessOptions, IQuickAccessProvider, IQuickAccessProviderDescriptor, IQuickAccessRegistry } from '../common/quickAccess.js';
1212
import { IQuickInputService, IQuickPick, IQuickPickItem, ItemActivation } from '../common/quickInput.js';
@@ -30,6 +30,15 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
3030
@IInstantiationService private readonly instantiationService: IInstantiationService
3131
) {
3232
super();
33+
this._register(toDisposable(() => {
34+
for (const provider of this.mapProviderToDescriptor.values()) {
35+
if (isDisposable(provider)) {
36+
provider.dispose();
37+
}
38+
}
39+
40+
this.visibleQuickAccess?.picker.dispose();
41+
}));
3342
}
3443

3544
pick(value = '', options?: IQuickAccessOptions): Promise<IQuickPickItem[] | undefined> {

src/vs/platform/registry/test/common/platform.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import assert from 'assert';
77
import { isFunction } from '../../../../base/common/types.js';
88
import { Registry } from '../../common/platform.js';
9+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
910

1011
suite('Platform / Registry', () => {
1112

13+
ensureNoDisposablesAreLeakedInTestSuite();
14+
1215
test('registry - api', function () {
1316
assert.ok(isFunction(Registry.add));
1417
assert.ok(isFunction(Registry.as));

src/vs/platform/workspace/test/common/workspace.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import { extUriBiasedIgnorePathCase } from '../../../../base/common/resources.js
1010
import { URI } from '../../../../base/common/uri.js';
1111
import { IRawFileWorkspaceFolder, Workspace, WorkspaceFolder } from '../../common/workspace.js';
1212
import { toWorkspaceFolders } from '../../../workspaces/common/workspaces.js';
13+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
1314

1415
suite('Workspace', () => {
1516

17+
ensureNoDisposablesAreLeakedInTestSuite();
18+
1619
const fileFolder = isWindows ? 'c:\\src' : '/src';
1720
const abcFolder = isWindows ? 'c:\\abc' : '/abc';
1821

src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
import assert from 'assert';
77
import { Query } from '../../common/extensionQuery.js';
8+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
89

910
suite('Extension query', () => {
11+
ensureNoDisposablesAreLeakedInTestSuite();
12+
1013
test('parse', () => {
1114
let query = Query.parse('');
1215
assert.strictEqual(query.value, '');

src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as matchers from '../../common/problemMatcher.js';
66

77
import assert from 'assert';
88
import { ValidationState, IProblemReporter, ValidationStatus } from '../../../../../base/common/parsers.js';
9+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
910

1011
class ProblemReporter implements IProblemReporter {
1112
private _validationStatus: ValidationStatus;
@@ -60,6 +61,8 @@ suite('ProblemPatternParser', () => {
6061
let parser: matchers.ProblemPatternParser;
6162
const testRegexp = new RegExp('test');
6263

64+
ensureNoDisposablesAreLeakedInTestSuite();
65+
6366
setup(() => {
6467
reporter = new ProblemReporter();
6568
parser = new matchers.ProblemPatternParser(reporter);

0 commit comments

Comments
 (0)