Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/common/lifecycle.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { Emitter } from '../../common/event.js';
8
import { DisposableStore, dispose, IDisposable, markAsSingleton, ReferenceCollection, thenIfNotDisposed, toDisposable } from '../../common/lifecycle.js';
9
import { ensureNoDisposablesAreLeakedInTestSuite, throwIfDisposablesAreLeaked } from './utils.js';
10
11
class Disposable implements IDisposable {
12
isDisposed = false;
13
dispose() { this.isDisposed = true; }
14
}
15
16
// Leaks are allowed here since we test lifecycle stuff:
17
// eslint-disable-next-line local/code-ensure-no-disposables-leak-in-test
18
suite('Lifecycle', () => {
19
test('dispose single disposable', () => {
20
const disposable = new Disposable();
21
22
assert(!disposable.isDisposed);
23
24
dispose(disposable);
25
26
assert(disposable.isDisposed);
27
});
28
29
test('dispose disposable array', () => {
30
const disposable = new Disposable();
31
const disposable2 = new Disposable();
32
33
assert(!disposable.isDisposed);
34
assert(!disposable2.isDisposed);
35
36
dispose([disposable, disposable2]);
37
38
assert(disposable.isDisposed);
39
assert(disposable2.isDisposed);
40
});
41
42
test('dispose disposables', () => {
43
const disposable = new Disposable();
44
const disposable2 = new Disposable();
45
46
assert(!disposable.isDisposed);
47
assert(!disposable2.isDisposed);
48
49
dispose(disposable);
50
dispose(disposable2);
51
52
assert(disposable.isDisposed);
53
assert(disposable2.isDisposed);
54
});
55
56
test('dispose array should dispose all if a child throws on dispose', () => {
57
const disposedValues = new Set<number>();
58
59
let thrownError: any;
60
try {
61
dispose([
62
toDisposable(() => { disposedValues.add(1); }),
63
toDisposable(() => { throw new Error('I am error'); }),
64
toDisposable(() => { disposedValues.add(3); }),
65
]);
66
} catch (e) {
67
thrownError = e;
68
}
69
70
assert.ok(disposedValues.has(1));
71
assert.ok(disposedValues.has(3));
72
assert.strictEqual(thrownError.message, 'I am error');
73
});
74
75
test('dispose array should rethrow composite error if multiple entries throw on dispose', () => {
76
const disposedValues = new Set<number>();
77
78
let thrownError: any;
79
try {
80
dispose([
81
toDisposable(() => { disposedValues.add(1); }),
82
toDisposable(() => { throw new Error('I am error 1'); }),
83
toDisposable(() => { throw new Error('I am error 2'); }),
84
toDisposable(() => { disposedValues.add(4); }),
85
]);
86
} catch (e) {
87
thrownError = e;
88
}
89
90
assert.ok(disposedValues.has(1));
91
assert.ok(disposedValues.has(4));
92
assert.ok(thrownError instanceof AggregateError);
93
assert.strictEqual((thrownError as AggregateError).errors.length, 2);
94
assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1');
95
assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2');
96
});
97
98
test('Action bar has broken accessibility #100273', function () {
99
const array = [{ dispose() { } }, { dispose() { } }];
100
const array2 = dispose(array);
101
102
assert.strictEqual(array.length, 2);
103
assert.strictEqual(array2.length, 0);
104
assert.ok(array !== array2);
105
106
const set = new Set<IDisposable>([{ dispose() { } }, { dispose() { } }]);
107
const setValues = set.values();
108
const setValues2 = dispose(setValues);
109
assert.ok(setValues === setValues2);
110
});
111
});
112
113
suite('DisposableStore', () => {
114
ensureNoDisposablesAreLeakedInTestSuite();
115
116
test('dispose should call all child disposes even if a child throws on dispose', () => {
117
const disposedValues = new Set<number>();
118
119
const store = new DisposableStore();
120
store.add(toDisposable(() => { disposedValues.add(1); }));
121
store.add(toDisposable(() => { throw new Error('I am error'); }));
122
store.add(toDisposable(() => { disposedValues.add(3); }));
123
124
let thrownError: any;
125
try {
126
store.dispose();
127
} catch (e) {
128
thrownError = e;
129
}
130
131
assert.ok(disposedValues.has(1));
132
assert.ok(disposedValues.has(3));
133
assert.strictEqual(thrownError.message, 'I am error');
134
});
135
136
test('dispose should throw composite error if multiple children throw on dispose', () => {
137
const disposedValues = new Set<number>();
138
139
const store = new DisposableStore();
140
store.add(toDisposable(() => { disposedValues.add(1); }));
141
store.add(toDisposable(() => { throw new Error('I am error 1'); }));
142
store.add(toDisposable(() => { throw new Error('I am error 2'); }));
143
store.add(toDisposable(() => { disposedValues.add(4); }));
144
145
let thrownError: any;
146
try {
147
store.dispose();
148
} catch (e) {
149
thrownError = e;
150
}
151
152
assert.ok(disposedValues.has(1));
153
assert.ok(disposedValues.has(4));
154
assert.ok(thrownError instanceof AggregateError);
155
assert.strictEqual((thrownError as AggregateError).errors.length, 2);
156
assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1');
157
assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2');
158
});
159
160
test('delete should evict and dispose of the disposables', () => {
161
const disposedValues = new Set<number>();
162
const disposables: IDisposable[] = [
163
toDisposable(() => { disposedValues.add(1); }),
164
toDisposable(() => { disposedValues.add(2); })
165
];
166
167
const store = new DisposableStore();
168
store.add(disposables[0]);
169
store.add(disposables[1]);
170
171
store.delete(disposables[0]);
172
173
assert.ok(disposedValues.has(1));
174
assert.ok(!disposedValues.has(2));
175
176
store.dispose();
177
178
assert.ok(disposedValues.has(1));
179
assert.ok(disposedValues.has(2));
180
});
181
182
test('deleteAndLeak should evict and not dispose of the disposables', () => {
183
const disposedValues = new Set<number>();
184
const disposables: IDisposable[] = [
185
toDisposable(() => { disposedValues.add(1); }),
186
toDisposable(() => { disposedValues.add(2); })
187
];
188
189
const store = new DisposableStore();
190
store.add(disposables[0]);
191
store.add(disposables[1]);
192
193
store.deleteAndLeak(disposables[0]);
194
195
assert.ok(!disposedValues.has(1));
196
assert.ok(!disposedValues.has(2));
197
198
store.dispose();
199
200
assert.ok(!disposedValues.has(1));
201
assert.ok(disposedValues.has(2));
202
203
disposables[0].dispose();
204
});
205
});
206
207
suite('Reference Collection', () => {
208
ensureNoDisposablesAreLeakedInTestSuite();
209
210
class Collection extends ReferenceCollection<number> {
211
private _count = 0;
212
get count() { return this._count; }
213
protected createReferencedObject(key: string): number { this._count++; return key.length; }
214
protected destroyReferencedObject(key: string, object: number): void { this._count--; }
215
}
216
217
test('simple', () => {
218
const collection = new Collection();
219
220
const ref1 = collection.acquire('test');
221
assert(ref1);
222
assert.strictEqual(ref1.object, 4);
223
assert.strictEqual(collection.count, 1);
224
ref1.dispose();
225
assert.strictEqual(collection.count, 0);
226
227
const ref2 = collection.acquire('test');
228
const ref3 = collection.acquire('test');
229
assert.strictEqual(ref2.object, ref3.object);
230
assert.strictEqual(collection.count, 1);
231
232
const ref4 = collection.acquire('monkey');
233
assert.strictEqual(ref4.object, 6);
234
assert.strictEqual(collection.count, 2);
235
236
ref2.dispose();
237
assert.strictEqual(collection.count, 2);
238
239
ref3.dispose();
240
assert.strictEqual(collection.count, 1);
241
242
ref4.dispose();
243
assert.strictEqual(collection.count, 0);
244
});
245
});
246
247
function assertThrows(fn: () => void, test: (error: any) => void) {
248
try {
249
fn();
250
assert.fail('Expected function to throw, but it did not.');
251
} catch (e) {
252
assert.ok(test(e));
253
}
254
}
255
256
suite('No Leakage Utilities', () => {
257
suite('throwIfDisposablesAreLeaked', () => {
258
test('throws if an event subscription is not cleaned up', () => {
259
const eventEmitter = new Emitter();
260
261
assertThrows(() => {
262
throwIfDisposablesAreLeaked(() => {
263
eventEmitter.event(() => {
264
// noop
265
});
266
}, false);
267
}, e => e.message.indexOf('undisposed disposables') !== -1);
268
});
269
270
test('throws if a disposable is not disposed', () => {
271
assertThrows(() => {
272
throwIfDisposablesAreLeaked(() => {
273
new DisposableStore();
274
}, false);
275
}, e => e.message.indexOf('undisposed disposables') !== -1);
276
});
277
278
test('does not throw if all event subscriptions are cleaned up', () => {
279
const eventEmitter = new Emitter();
280
throwIfDisposablesAreLeaked(() => {
281
eventEmitter.event(() => {
282
// noop
283
}).dispose();
284
});
285
});
286
287
test('does not throw if all disposables are disposed', () => {
288
// This disposable is reported before the test and not tracked.
289
toDisposable(() => { });
290
291
throwIfDisposablesAreLeaked(() => {
292
// This disposable is marked as singleton
293
markAsSingleton(toDisposable(() => { }));
294
295
// These disposables are also marked as singleton
296
const disposableStore = new DisposableStore();
297
disposableStore.add(toDisposable(() => { }));
298
markAsSingleton(disposableStore);
299
300
toDisposable(() => { }).dispose();
301
});
302
});
303
});
304
305
suite('ensureNoDisposablesAreLeakedInTest', () => {
306
ensureNoDisposablesAreLeakedInTestSuite();
307
308
test('Basic Test', () => {
309
toDisposable(() => { }).dispose();
310
});
311
});
312
313
suite('thenIfNotDisposed', () => {
314
const store = ensureNoDisposablesAreLeakedInTestSuite();
315
316
test('normal case', async () => {
317
let called = false;
318
store.add(thenIfNotDisposed(Promise.resolve(123), (result: number) => {
319
assert.strictEqual(result, 123);
320
called = true;
321
}));
322
323
await new Promise(resolve => setTimeout(resolve, 0));
324
assert.strictEqual(called, true);
325
});
326
327
test('disposed before promise resolves', async () => {
328
let called = false;
329
const disposable = thenIfNotDisposed(Promise.resolve(123), () => {
330
called = true;
331
});
332
333
disposable.dispose();
334
await new Promise(resolve => setTimeout(resolve, 0));
335
assert.strictEqual(called, false);
336
});
337
});
338
});
339
340