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
5250 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 { DisposableSet, 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('DisposableSet', () => {
208
ensureNoDisposablesAreLeakedInTestSuite();
209
210
test('dispose should dispose all values and mark as disposed', () => {
211
const disposedValues = new Set<number>();
212
213
const set = new DisposableSet<IDisposable>();
214
set.add(toDisposable(() => { disposedValues.add(1); }));
215
set.add(toDisposable(() => { disposedValues.add(2); }));
216
set.add(toDisposable(() => { disposedValues.add(3); }));
217
218
assert.strictEqual(set.size, 3);
219
220
set.dispose();
221
222
assert.ok(disposedValues.has(1));
223
assert.ok(disposedValues.has(2));
224
assert.ok(disposedValues.has(3));
225
assert.strictEqual(set.size, 0);
226
});
227
228
test('dispose should call all child disposes even if a child throws on dispose', () => {
229
const disposedValues = new Set<number>();
230
231
const set = new DisposableSet<IDisposable>();
232
set.add(toDisposable(() => { disposedValues.add(1); }));
233
set.add(toDisposable(() => { throw new Error('I am error'); }));
234
set.add(toDisposable(() => { disposedValues.add(3); }));
235
236
let thrownError: any;
237
try {
238
set.dispose();
239
} catch (e) {
240
thrownError = e;
241
}
242
243
assert.ok(disposedValues.has(1));
244
assert.ok(disposedValues.has(3));
245
assert.strictEqual(thrownError.message, 'I am error');
246
});
247
248
test('clearAndDisposeAll should dispose values but not mark as disposed', () => {
249
const disposedValues = new Set<number>();
250
251
const set = new DisposableSet<IDisposable>();
252
const d1 = toDisposable(() => { disposedValues.add(1); });
253
set.add(d1);
254
255
set.clearAndDisposeAll();
256
257
assert.ok(disposedValues.has(1));
258
assert.strictEqual(set.size, 0);
259
260
// Can still add new values
261
const d2 = toDisposable(() => { disposedValues.add(2); });
262
set.add(d2);
263
assert.strictEqual(set.size, 1);
264
265
set.dispose();
266
assert.ok(disposedValues.has(2));
267
});
268
269
test('has should return true if value exists', () => {
270
const set = new DisposableSet<IDisposable>();
271
const d = toDisposable(() => { });
272
set.add(d);
273
274
const other = toDisposable(() => { });
275
assert.ok(set.has(d));
276
assert.ok(!set.has(other));
277
278
set.dispose();
279
other.dispose();
280
});
281
282
test('deleteAndDispose should remove and dispose the value', () => {
283
const disposedValues = new Set<number>();
284
285
const set = new DisposableSet<IDisposable>();
286
const d1 = toDisposable(() => { disposedValues.add(1); });
287
const d2 = toDisposable(() => { disposedValues.add(2); });
288
set.add(d1);
289
set.add(d2);
290
291
set.deleteAndDispose(d1);
292
293
assert.ok(disposedValues.has(1));
294
assert.ok(!disposedValues.has(2));
295
assert.strictEqual(set.size, 1);
296
assert.ok(!set.has(d1));
297
assert.ok(set.has(d2));
298
299
set.dispose();
300
assert.ok(disposedValues.has(2));
301
});
302
303
test('deleteAndLeak should remove but not dispose the value', () => {
304
const disposedValues = new Set<number>();
305
306
const set = new DisposableSet<IDisposable>();
307
const d1 = toDisposable(() => { disposedValues.add(1); });
308
const d2 = toDisposable(() => { disposedValues.add(2); });
309
set.add(d1);
310
set.add(d2);
311
312
const leaked = set.deleteAndLeak(d1);
313
314
assert.strictEqual(leaked, d1);
315
assert.ok(!disposedValues.has(1));
316
assert.ok(!disposedValues.has(2));
317
assert.strictEqual(set.size, 1);
318
319
set.dispose();
320
321
assert.ok(!disposedValues.has(1));
322
assert.ok(disposedValues.has(2));
323
324
// Caller is responsible for disposing
325
d1.dispose();
326
});
327
328
test('deleteAndLeak should return undefined if value not in set', () => {
329
const set = new DisposableSet<IDisposable>();
330
const d = toDisposable(() => { });
331
332
const leaked = set.deleteAndLeak(d);
333
334
assert.strictEqual(leaked, undefined);
335
336
set.dispose();
337
d.dispose();
338
});
339
340
test('values should iterate over all values', () => {
341
const set = new DisposableSet<IDisposable>();
342
const d1 = toDisposable(() => { });
343
const d2 = toDisposable(() => { });
344
set.add(d1);
345
set.add(d2);
346
347
const values = [...set.values()];
348
assert.strictEqual(values.length, 2);
349
assert.ok(values.includes(d1));
350
assert.ok(values.includes(d2));
351
352
set.dispose();
353
});
354
355
test('Symbol.iterator should allow for-of iteration', () => {
356
const set = new DisposableSet<IDisposable>();
357
const d1 = toDisposable(() => { });
358
const d2 = toDisposable(() => { });
359
set.add(d1);
360
set.add(d2);
361
362
const values: IDisposable[] = [];
363
for (const v of set) {
364
values.push(v);
365
}
366
367
assert.strictEqual(values.length, 2);
368
assert.ok(values.includes(d1));
369
assert.ok(values.includes(d2));
370
371
set.dispose();
372
});
373
});
374
375
suite('Reference Collection', () => {
376
ensureNoDisposablesAreLeakedInTestSuite();
377
378
class Collection extends ReferenceCollection<number> {
379
private _count = 0;
380
get count() { return this._count; }
381
protected createReferencedObject(key: string): number { this._count++; return key.length; }
382
protected destroyReferencedObject(key: string, object: number): void { this._count--; }
383
}
384
385
test('simple', () => {
386
const collection = new Collection();
387
388
const ref1 = collection.acquire('test');
389
assert(ref1);
390
assert.strictEqual(ref1.object, 4);
391
assert.strictEqual(collection.count, 1);
392
ref1.dispose();
393
assert.strictEqual(collection.count, 0);
394
395
const ref2 = collection.acquire('test');
396
const ref3 = collection.acquire('test');
397
assert.strictEqual(ref2.object, ref3.object);
398
assert.strictEqual(collection.count, 1);
399
400
const ref4 = collection.acquire('monkey');
401
assert.strictEqual(ref4.object, 6);
402
assert.strictEqual(collection.count, 2);
403
404
ref2.dispose();
405
assert.strictEqual(collection.count, 2);
406
407
ref3.dispose();
408
assert.strictEqual(collection.count, 1);
409
410
ref4.dispose();
411
assert.strictEqual(collection.count, 0);
412
});
413
});
414
415
function assertThrows(fn: () => void, test: (error: any) => void) {
416
try {
417
fn();
418
assert.fail('Expected function to throw, but it did not.');
419
} catch (e) {
420
assert.ok(test(e));
421
}
422
}
423
424
suite('No Leakage Utilities', () => {
425
suite('throwIfDisposablesAreLeaked', () => {
426
test('throws if an event subscription is not cleaned up', () => {
427
const eventEmitter = new Emitter();
428
429
assertThrows(() => {
430
throwIfDisposablesAreLeaked(() => {
431
eventEmitter.event(() => {
432
// noop
433
});
434
}, false);
435
}, e => e.message.indexOf('undisposed disposables') !== -1);
436
});
437
438
test('throws if a disposable is not disposed', () => {
439
assertThrows(() => {
440
throwIfDisposablesAreLeaked(() => {
441
new DisposableStore();
442
}, false);
443
}, e => e.message.indexOf('undisposed disposables') !== -1);
444
});
445
446
test('does not throw if all event subscriptions are cleaned up', () => {
447
const eventEmitter = new Emitter();
448
throwIfDisposablesAreLeaked(() => {
449
eventEmitter.event(() => {
450
// noop
451
}).dispose();
452
});
453
});
454
455
test('does not throw if all disposables are disposed', () => {
456
// This disposable is reported before the test and not tracked.
457
toDisposable(() => { });
458
459
throwIfDisposablesAreLeaked(() => {
460
// This disposable is marked as singleton
461
markAsSingleton(toDisposable(() => { }));
462
463
// These disposables are also marked as singleton
464
const disposableStore = new DisposableStore();
465
disposableStore.add(toDisposable(() => { }));
466
markAsSingleton(disposableStore);
467
468
toDisposable(() => { }).dispose();
469
});
470
});
471
});
472
473
suite('ensureNoDisposablesAreLeakedInTest', () => {
474
ensureNoDisposablesAreLeakedInTestSuite();
475
476
test('Basic Test', () => {
477
toDisposable(() => { }).dispose();
478
});
479
});
480
481
suite('thenIfNotDisposed', () => {
482
const store = ensureNoDisposablesAreLeakedInTestSuite();
483
484
test('normal case', async () => {
485
let called = false;
486
store.add(thenIfNotDisposed(Promise.resolve(123), (result: number) => {
487
assert.strictEqual(result, 123);
488
called = true;
489
}));
490
491
await new Promise(resolve => setTimeout(resolve, 0));
492
assert.strictEqual(called, true);
493
});
494
495
test('disposed before promise resolves', async () => {
496
let called = false;
497
const disposable = thenIfNotDisposed(Promise.resolve(123), () => {
498
called = true;
499
});
500
501
disposable.dispose();
502
await new Promise(resolve => setTimeout(resolve, 0));
503
assert.strictEqual(called, false);
504
});
505
});
506
});
507
508