Path: blob/main/src/vs/base/test/common/lifecycle.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Emitter } from '../../common/event.js';7import { DisposableStore, dispose, IDisposable, markAsSingleton, ReferenceCollection, thenIfNotDisposed, toDisposable } from '../../common/lifecycle.js';8import { ensureNoDisposablesAreLeakedInTestSuite, throwIfDisposablesAreLeaked } from './utils.js';910class Disposable implements IDisposable {11isDisposed = false;12dispose() { this.isDisposed = true; }13}1415// Leaks are allowed here since we test lifecycle stuff:16// eslint-disable-next-line local/code-ensure-no-disposables-leak-in-test17suite('Lifecycle', () => {18test('dispose single disposable', () => {19const disposable = new Disposable();2021assert(!disposable.isDisposed);2223dispose(disposable);2425assert(disposable.isDisposed);26});2728test('dispose disposable array', () => {29const disposable = new Disposable();30const disposable2 = new Disposable();3132assert(!disposable.isDisposed);33assert(!disposable2.isDisposed);3435dispose([disposable, disposable2]);3637assert(disposable.isDisposed);38assert(disposable2.isDisposed);39});4041test('dispose disposables', () => {42const disposable = new Disposable();43const disposable2 = new Disposable();4445assert(!disposable.isDisposed);46assert(!disposable2.isDisposed);4748dispose(disposable);49dispose(disposable2);5051assert(disposable.isDisposed);52assert(disposable2.isDisposed);53});5455test('dispose array should dispose all if a child throws on dispose', () => {56const disposedValues = new Set<number>();5758let thrownError: any;59try {60dispose([61toDisposable(() => { disposedValues.add(1); }),62toDisposable(() => { throw new Error('I am error'); }),63toDisposable(() => { disposedValues.add(3); }),64]);65} catch (e) {66thrownError = e;67}6869assert.ok(disposedValues.has(1));70assert.ok(disposedValues.has(3));71assert.strictEqual(thrownError.message, 'I am error');72});7374test('dispose array should rethrow composite error if multiple entries throw on dispose', () => {75const disposedValues = new Set<number>();7677let thrownError: any;78try {79dispose([80toDisposable(() => { disposedValues.add(1); }),81toDisposable(() => { throw new Error('I am error 1'); }),82toDisposable(() => { throw new Error('I am error 2'); }),83toDisposable(() => { disposedValues.add(4); }),84]);85} catch (e) {86thrownError = e;87}8889assert.ok(disposedValues.has(1));90assert.ok(disposedValues.has(4));91assert.ok(thrownError instanceof AggregateError);92assert.strictEqual((thrownError as AggregateError).errors.length, 2);93assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1');94assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2');95});9697test('Action bar has broken accessibility #100273', function () {98const array = [{ dispose() { } }, { dispose() { } }];99const array2 = dispose(array);100101assert.strictEqual(array.length, 2);102assert.strictEqual(array2.length, 0);103assert.ok(array !== array2);104105const set = new Set<IDisposable>([{ dispose() { } }, { dispose() { } }]);106const setValues = set.values();107const setValues2 = dispose(setValues);108assert.ok(setValues === setValues2);109});110});111112suite('DisposableStore', () => {113ensureNoDisposablesAreLeakedInTestSuite();114115test('dispose should call all child disposes even if a child throws on dispose', () => {116const disposedValues = new Set<number>();117118const store = new DisposableStore();119store.add(toDisposable(() => { disposedValues.add(1); }));120store.add(toDisposable(() => { throw new Error('I am error'); }));121store.add(toDisposable(() => { disposedValues.add(3); }));122123let thrownError: any;124try {125store.dispose();126} catch (e) {127thrownError = e;128}129130assert.ok(disposedValues.has(1));131assert.ok(disposedValues.has(3));132assert.strictEqual(thrownError.message, 'I am error');133});134135test('dispose should throw composite error if multiple children throw on dispose', () => {136const disposedValues = new Set<number>();137138const store = new DisposableStore();139store.add(toDisposable(() => { disposedValues.add(1); }));140store.add(toDisposable(() => { throw new Error('I am error 1'); }));141store.add(toDisposable(() => { throw new Error('I am error 2'); }));142store.add(toDisposable(() => { disposedValues.add(4); }));143144let thrownError: any;145try {146store.dispose();147} catch (e) {148thrownError = e;149}150151assert.ok(disposedValues.has(1));152assert.ok(disposedValues.has(4));153assert.ok(thrownError instanceof AggregateError);154assert.strictEqual((thrownError as AggregateError).errors.length, 2);155assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1');156assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2');157});158159test('delete should evict and dispose of the disposables', () => {160const disposedValues = new Set<number>();161const disposables: IDisposable[] = [162toDisposable(() => { disposedValues.add(1); }),163toDisposable(() => { disposedValues.add(2); })164];165166const store = new DisposableStore();167store.add(disposables[0]);168store.add(disposables[1]);169170store.delete(disposables[0]);171172assert.ok(disposedValues.has(1));173assert.ok(!disposedValues.has(2));174175store.dispose();176177assert.ok(disposedValues.has(1));178assert.ok(disposedValues.has(2));179});180181test('deleteAndLeak should evict and not dispose of the disposables', () => {182const disposedValues = new Set<number>();183const disposables: IDisposable[] = [184toDisposable(() => { disposedValues.add(1); }),185toDisposable(() => { disposedValues.add(2); })186];187188const store = new DisposableStore();189store.add(disposables[0]);190store.add(disposables[1]);191192store.deleteAndLeak(disposables[0]);193194assert.ok(!disposedValues.has(1));195assert.ok(!disposedValues.has(2));196197store.dispose();198199assert.ok(!disposedValues.has(1));200assert.ok(disposedValues.has(2));201202disposables[0].dispose();203});204});205206suite('Reference Collection', () => {207ensureNoDisposablesAreLeakedInTestSuite();208209class Collection extends ReferenceCollection<number> {210private _count = 0;211get count() { return this._count; }212protected createReferencedObject(key: string): number { this._count++; return key.length; }213protected destroyReferencedObject(key: string, object: number): void { this._count--; }214}215216test('simple', () => {217const collection = new Collection();218219const ref1 = collection.acquire('test');220assert(ref1);221assert.strictEqual(ref1.object, 4);222assert.strictEqual(collection.count, 1);223ref1.dispose();224assert.strictEqual(collection.count, 0);225226const ref2 = collection.acquire('test');227const ref3 = collection.acquire('test');228assert.strictEqual(ref2.object, ref3.object);229assert.strictEqual(collection.count, 1);230231const ref4 = collection.acquire('monkey');232assert.strictEqual(ref4.object, 6);233assert.strictEqual(collection.count, 2);234235ref2.dispose();236assert.strictEqual(collection.count, 2);237238ref3.dispose();239assert.strictEqual(collection.count, 1);240241ref4.dispose();242assert.strictEqual(collection.count, 0);243});244});245246function assertThrows(fn: () => void, test: (error: any) => void) {247try {248fn();249assert.fail('Expected function to throw, but it did not.');250} catch (e) {251assert.ok(test(e));252}253}254255suite('No Leakage Utilities', () => {256suite('throwIfDisposablesAreLeaked', () => {257test('throws if an event subscription is not cleaned up', () => {258const eventEmitter = new Emitter();259260assertThrows(() => {261throwIfDisposablesAreLeaked(() => {262eventEmitter.event(() => {263// noop264});265}, false);266}, e => e.message.indexOf('undisposed disposables') !== -1);267});268269test('throws if a disposable is not disposed', () => {270assertThrows(() => {271throwIfDisposablesAreLeaked(() => {272new DisposableStore();273}, false);274}, e => e.message.indexOf('undisposed disposables') !== -1);275});276277test('does not throw if all event subscriptions are cleaned up', () => {278const eventEmitter = new Emitter();279throwIfDisposablesAreLeaked(() => {280eventEmitter.event(() => {281// noop282}).dispose();283});284});285286test('does not throw if all disposables are disposed', () => {287// This disposable is reported before the test and not tracked.288toDisposable(() => { });289290throwIfDisposablesAreLeaked(() => {291// This disposable is marked as singleton292markAsSingleton(toDisposable(() => { }));293294// These disposables are also marked as singleton295const disposableStore = new DisposableStore();296disposableStore.add(toDisposable(() => { }));297markAsSingleton(disposableStore);298299toDisposable(() => { }).dispose();300});301});302});303304suite('ensureNoDisposablesAreLeakedInTest', () => {305ensureNoDisposablesAreLeakedInTestSuite();306307test('Basic Test', () => {308toDisposable(() => { }).dispose();309});310});311312suite('thenIfNotDisposed', () => {313const store = ensureNoDisposablesAreLeakedInTestSuite();314315test('normal case', async () => {316let called = false;317store.add(thenIfNotDisposed(Promise.resolve(123), (result: number) => {318assert.strictEqual(result, 123);319called = true;320}));321322await new Promise(resolve => setTimeout(resolve, 0));323assert.strictEqual(called, true);324});325326test('disposed before promise resolves', async () => {327let called = false;328const disposable = thenIfNotDisposed(Promise.resolve(123), () => {329called = true;330});331332disposable.dispose();333await new Promise(resolve => setTimeout(resolve, 0));334assert.strictEqual(called, false);335});336});337});338339340