Path: blob/main/src/vs/platform/instantiation/test/common/instantiationService.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, Event } from '../../../../base/common/event.js';7import { dispose } from '../../../../base/common/lifecycle.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';9import { SyncDescriptor } from '../../common/descriptors.js';10import { createDecorator, IInstantiationService, ServicesAccessor } from '../../common/instantiation.js';11import { InstantiationService } from '../../common/instantiationService.js';12import { ServiceCollection } from '../../common/serviceCollection.js';1314const IService1 = createDecorator<IService1>('service1');1516interface IService1 {17readonly _serviceBrand: undefined;18c: number;19}2021class Service1 implements IService1 {22declare readonly _serviceBrand: undefined;23c = 1;24}2526const IService2 = createDecorator<IService2>('service2');2728interface IService2 {29readonly _serviceBrand: undefined;30d: boolean;31}3233class Service2 implements IService2 {34declare readonly _serviceBrand: undefined;35d = true;36}3738const IService3 = createDecorator<IService3>('service3');3940interface IService3 {41readonly _serviceBrand: undefined;42s: string;43}4445class Service3 implements IService3 {46declare readonly _serviceBrand: undefined;47s = 'farboo';48}4950const IDependentService = createDecorator<IDependentService>('dependentService');5152interface IDependentService {53readonly _serviceBrand: undefined;54name: string;55}5657class DependentService implements IDependentService {58declare readonly _serviceBrand: undefined;59constructor(@IService1 service: IService1) {60assert.strictEqual(service.c, 1);61}6263name = 'farboo';64}6566class Service1Consumer {6768constructor(@IService1 service1: IService1) {69assert.ok(service1);70assert.strictEqual(service1.c, 1);71}72}7374class Target2Dep {7576constructor(@IService1 service1: IService1, @IService2 service2: Service2) {77assert.ok(service1 instanceof Service1);78assert.ok(service2 instanceof Service2);79}80}8182class TargetWithStaticParam {83constructor(v: boolean, @IService1 service1: IService1) {84assert.ok(v);85assert.ok(service1);86assert.strictEqual(service1.c, 1);87}88}89909192class DependentServiceTarget {93constructor(@IDependentService d: IDependentService) {94assert.ok(d);95assert.strictEqual(d.name, 'farboo');96}97}9899class DependentServiceTarget2 {100constructor(@IDependentService d: IDependentService, @IService1 s: IService1) {101assert.ok(d);102assert.strictEqual(d.name, 'farboo');103assert.ok(s);104assert.strictEqual(s.c, 1);105}106}107108109class ServiceLoop1 implements IService1 {110declare readonly _serviceBrand: undefined;111c = 1;112113constructor(@IService2 s: IService2) {114115}116}117118class ServiceLoop2 implements IService2 {119declare readonly _serviceBrand: undefined;120d = true;121122constructor(@IService1 s: IService1) {123124}125}126127suite('Instantiation Service', () => {128129test('service collection, cannot overwrite', function () {130const collection = new ServiceCollection();131let result = collection.set(IService1, null!);132assert.strictEqual(result, undefined);133result = collection.set(IService1, new Service1());134assert.strictEqual(result, null);135});136137test('service collection, add/has', function () {138const collection = new ServiceCollection();139collection.set(IService1, null!);140assert.ok(collection.has(IService1));141142collection.set(IService2, null!);143assert.ok(collection.has(IService1));144assert.ok(collection.has(IService2));145});146147test('@Param - simple clase', function () {148const collection = new ServiceCollection();149const service = new InstantiationService(collection);150collection.set(IService1, new Service1());151collection.set(IService2, new Service2());152collection.set(IService3, new Service3());153154service.createInstance(Service1Consumer);155});156157test('@Param - fixed args', function () {158const collection = new ServiceCollection();159const service = new InstantiationService(collection);160collection.set(IService1, new Service1());161collection.set(IService2, new Service2());162collection.set(IService3, new Service3());163164service.createInstance(TargetWithStaticParam, true);165});166167test('service collection is live', function () {168169const collection = new ServiceCollection();170collection.set(IService1, new Service1());171172const service = new InstantiationService(collection);173service.createInstance(Service1Consumer);174175collection.set(IService2, new Service2());176177service.createInstance(Target2Dep);178service.invokeFunction(function (a) {179assert.ok(a.get(IService1));180assert.ok(a.get(IService2));181});182});183184// we made this a warning185// test('@Param - too many args', function () {186// let service = instantiationService.create(Object.create(null));187// service.addSingleton(IService1, new Service1());188// service.addSingleton(IService2, new Service2());189// service.addSingleton(IService3, new Service3());190191// assert.throws(() => service.createInstance(ParameterTarget2, true, 2));192// });193194// test('@Param - too few args', function () {195// let service = instantiationService.create(Object.create(null));196// service.addSingleton(IService1, new Service1());197// service.addSingleton(IService2, new Service2());198// service.addSingleton(IService3, new Service3());199200// assert.throws(() => service.createInstance(ParameterTarget2));201// });202203test('SyncDesc - no dependencies', function () {204const collection = new ServiceCollection();205const service = new InstantiationService(collection);206collection.set(IService1, new SyncDescriptor<IService1>(Service1));207208service.invokeFunction(accessor => {209210const service1 = accessor.get(IService1);211assert.ok(service1);212assert.strictEqual(service1.c, 1);213214const service2 = accessor.get(IService1);215assert.ok(service1 === service2);216});217});218219test('SyncDesc - service with service dependency', function () {220const collection = new ServiceCollection();221const service = new InstantiationService(collection);222collection.set(IService1, new SyncDescriptor<IService1>(Service1));223collection.set(IDependentService, new SyncDescriptor<IDependentService>(DependentService));224225service.invokeFunction(accessor => {226const d = accessor.get(IDependentService);227assert.ok(d);228assert.strictEqual(d.name, 'farboo');229});230});231232test('SyncDesc - target depends on service future', function () {233const collection = new ServiceCollection();234const service = new InstantiationService(collection);235collection.set(IService1, new SyncDescriptor<IService1>(Service1));236collection.set(IDependentService, new SyncDescriptor<IDependentService>(DependentService));237238const d = service.createInstance(DependentServiceTarget);239assert.ok(d instanceof DependentServiceTarget);240241const d2 = service.createInstance(DependentServiceTarget2);242assert.ok(d2 instanceof DependentServiceTarget2);243});244245test('SyncDesc - explode on loop', function () {246const collection = new ServiceCollection();247const service = new InstantiationService(collection);248collection.set(IService1, new SyncDescriptor<IService1>(ServiceLoop1));249collection.set(IService2, new SyncDescriptor<IService2>(ServiceLoop2));250251assert.throws(() => {252service.invokeFunction(accessor => {253accessor.get(IService1);254});255});256assert.throws(() => {257service.invokeFunction(accessor => {258accessor.get(IService2);259});260});261262try {263service.invokeFunction(accessor => {264accessor.get(IService1);265});266} catch (err) {267assert.ok(err.name);268assert.ok(err.message);269}270});271272test('Invoke - get services', function () {273const collection = new ServiceCollection();274const service = new InstantiationService(collection);275collection.set(IService1, new Service1());276collection.set(IService2, new Service2());277278function test(accessor: ServicesAccessor) {279assert.ok(accessor.get(IService1) instanceof Service1);280assert.strictEqual(accessor.get(IService1).c, 1);281282return true;283}284285assert.strictEqual(service.invokeFunction(test), true);286});287288test('Invoke - get service, optional', function () {289const collection = new ServiceCollection([IService1, new Service1()]);290const service = new InstantiationService(collection);291292function test(accessor: ServicesAccessor) {293assert.ok(accessor.get(IService1) instanceof Service1);294assert.throws(() => accessor.get(IService2));295return true;296}297assert.strictEqual(service.invokeFunction(test), true);298});299300test('Invoke - keeping accessor NOT allowed', function () {301const collection = new ServiceCollection();302const service = new InstantiationService(collection);303collection.set(IService1, new Service1());304collection.set(IService2, new Service2());305306let cached: ServicesAccessor;307308function test(accessor: ServicesAccessor) {309assert.ok(accessor.get(IService1) instanceof Service1);310assert.strictEqual(accessor.get(IService1).c, 1);311cached = accessor;312return true;313}314315assert.strictEqual(service.invokeFunction(test), true);316317assert.throws(() => cached.get(IService2));318});319320test('Invoke - throw error', function () {321const collection = new ServiceCollection();322const service = new InstantiationService(collection);323collection.set(IService1, new Service1());324collection.set(IService2, new Service2());325326function test(accessor: ServicesAccessor) {327throw new Error();328}329330assert.throws(() => service.invokeFunction(test));331});332333test('Create child', function () {334335let serviceInstanceCount = 0;336337const CtorCounter = class implements Service1 {338declare readonly _serviceBrand: undefined;339c = 1;340constructor() {341serviceInstanceCount += 1;342}343};344345// creating the service instance BEFORE the child service346let service = new InstantiationService(new ServiceCollection([IService1, new SyncDescriptor(CtorCounter)]));347service.createInstance(Service1Consumer);348349// second instance must be earlier ONE350let child = service.createChild(new ServiceCollection([IService2, new Service2()]));351child.createInstance(Service1Consumer);352353assert.strictEqual(serviceInstanceCount, 1);354355// creating the service instance AFTER the child service356serviceInstanceCount = 0;357service = new InstantiationService(new ServiceCollection([IService1, new SyncDescriptor(CtorCounter)]));358child = service.createChild(new ServiceCollection([IService2, new Service2()]));359360// second instance must be earlier ONE361service.createInstance(Service1Consumer);362child.createInstance(Service1Consumer);363364assert.strictEqual(serviceInstanceCount, 1);365});366367test('Remote window / integration tests is broken #105562', function () {368369const Service1 = createDecorator<any>('service1');370class Service1Impl {371constructor(@IInstantiationService insta: IInstantiationService) {372const c = insta.invokeFunction(accessor => accessor.get(Service2)); // THIS is the recursive call373assert.ok(c);374}375}376const Service2 = createDecorator<any>('service2');377class Service2Impl {378constructor() { }379}380381// This service depends on Service1 and Service2 BUT creating Service1 creates Service2 (via recursive invocation)382// and then Servce2 should not be created a second time383const Service21 = createDecorator<any>('service21');384class Service21Impl {385constructor(@Service2 public readonly service2: Service2Impl, @Service1 public readonly service1: Service1Impl) { }386}387388const insta = new InstantiationService(new ServiceCollection(389[Service1, new SyncDescriptor(Service1Impl)],390[Service2, new SyncDescriptor(Service2Impl)],391[Service21, new SyncDescriptor(Service21Impl)],392));393394const obj = insta.invokeFunction(accessor => accessor.get(Service21));395assert.ok(obj);396});397398test('Sync/Async dependency loop', async function () {399400const A = createDecorator<A>('A');401const B = createDecorator<B>('B');402interface A { _serviceBrand: undefined; doIt(): void }403interface B { _serviceBrand: undefined; b(): boolean }404405class BConsumer {406constructor(@B private readonly b: B) {407408}409doIt() {410return this.b.b();411}412}413414class AService implements A {415_serviceBrand: undefined;416prop: BConsumer;417constructor(@IInstantiationService insta: IInstantiationService) {418this.prop = insta.createInstance(BConsumer);419}420doIt() {421return this.prop.doIt();422}423}424425class BService implements B {426_serviceBrand: undefined;427constructor(@A a: A) {428assert.ok(a);429}430b() { return true; }431}432433// SYNC -> explodes AImpl -> [insta:BConsumer] -> BImpl -> AImpl434{435const insta1 = new InstantiationService(new ServiceCollection(436[A, new SyncDescriptor(AService)],437[B, new SyncDescriptor(BService)],438), true, undefined, true);439440try {441insta1.invokeFunction(accessor => accessor.get(A));442assert.ok(false);443444} catch (error) {445assert.ok(error instanceof Error);446assert.ok(error.message.includes('RECURSIVELY'));447}448}449450// ASYNC -> doesn't explode but cycle is tracked451{452const insta2 = new InstantiationService(new ServiceCollection(453[A, new SyncDescriptor(AService, undefined, true)],454[B, new SyncDescriptor(BService, undefined)],455), true, undefined, true);456457const a = insta2.invokeFunction(accessor => accessor.get(A));458a.doIt();459460const cycle = insta2._globalGraph?.findCycleSlow();461assert.strictEqual(cycle, 'A -> B -> A');462}463});464465test('Delayed and events', function () {466const A = createDecorator<A>('A');467interface A {468_serviceBrand: undefined;469onDidDoIt: Event<any>;470doIt(): void;471}472473let created = false;474class AImpl implements A {475_serviceBrand: undefined;476_doIt = 0;477478_onDidDoIt = new Emitter<this>();479onDidDoIt: Event<this> = this._onDidDoIt.event;480481constructor() {482created = true;483}484485doIt(): void {486this._doIt += 1;487this._onDidDoIt.fire(this);488}489}490491const insta = new InstantiationService(new ServiceCollection(492[A, new SyncDescriptor(AImpl, undefined, true)],493), true, undefined, true);494495class Consumer {496constructor(@A public readonly a: A) {497// eager subscribe -> NO service instance498}499}500501const c: Consumer = insta.createInstance(Consumer);502let eventCount = 0;503504// subscribing to event doesn't trigger instantiation505const listener = (e: any) => {506assert.ok(e instanceof AImpl);507eventCount++;508};509const d1 = c.a.onDidDoIt(listener);510const d2 = c.a.onDidDoIt(listener);511assert.strictEqual(created, false);512assert.strictEqual(eventCount, 0);513d2.dispose();514515// instantiation happens on first call516c.a.doIt();517assert.strictEqual(created, true);518assert.strictEqual(eventCount, 1);519520521const d3 = c.a.onDidDoIt(listener);522c.a.doIt();523assert.strictEqual(eventCount, 3);524525dispose([d1, d3]);526});527528529test('Capture event before init, use after init', function () {530const A = createDecorator<A>('A');531interface A {532_serviceBrand: undefined;533onDidDoIt: Event<any>;534doIt(): void;535noop(): void;536}537538let created = false;539class AImpl implements A {540_serviceBrand: undefined;541_doIt = 0;542543_onDidDoIt = new Emitter<this>();544onDidDoIt: Event<this> = this._onDidDoIt.event;545546constructor() {547created = true;548}549550doIt(): void {551this._doIt += 1;552this._onDidDoIt.fire(this);553}554555noop(): void {556}557}558559const insta = new InstantiationService(new ServiceCollection(560[A, new SyncDescriptor(AImpl, undefined, true)],561), true, undefined, true);562563class Consumer {564constructor(@A public readonly a: A) {565// eager subscribe -> NO service instance566}567}568569const c: Consumer = insta.createInstance(Consumer);570let eventCount = 0;571572// subscribing to event doesn't trigger instantiation573const listener = (e: any) => {574assert.ok(e instanceof AImpl);575eventCount++;576};577578const event = c.a.onDidDoIt;579580// const d1 = c.a.onDidDoIt(listener);581assert.strictEqual(created, false);582583c.a.noop();584assert.strictEqual(created, true);585586const d1 = event(listener);587588c.a.doIt();589590591// instantiation happens on first call592assert.strictEqual(eventCount, 1);593594dispose(d1);595});596597test('Dispose early event listener', function () {598const A = createDecorator<A>('A');599interface A {600_serviceBrand: undefined;601onDidDoIt: Event<any>;602doIt(): void;603}604let created = false;605class AImpl implements A {606_serviceBrand: undefined;607_doIt = 0;608609_onDidDoIt = new Emitter<this>();610onDidDoIt: Event<this> = this._onDidDoIt.event;611612constructor() {613created = true;614}615616doIt(): void {617this._doIt += 1;618this._onDidDoIt.fire(this);619}620}621622const insta = new InstantiationService(new ServiceCollection(623[A, new SyncDescriptor(AImpl, undefined, true)],624), true, undefined, true);625626class Consumer {627constructor(@A public readonly a: A) {628// eager subscribe -> NO service instance629}630}631632const c: Consumer = insta.createInstance(Consumer);633let eventCount = 0;634635// subscribing to event doesn't trigger instantiation636const listener = (e: any) => {637assert.ok(e instanceof AImpl);638eventCount++;639};640641const d1 = c.a.onDidDoIt(listener);642assert.strictEqual(created, false);643assert.strictEqual(eventCount, 0);644645c.a.doIt();646647// instantiation happens on first call648assert.strictEqual(created, true);649assert.strictEqual(eventCount, 1);650651dispose(d1);652653c.a.doIt();654assert.strictEqual(eventCount, 1);655});656657658test('Dispose services it created', function () {659let disposedA = false;660let disposedB = false;661662const A = createDecorator<A>('A');663interface A {664_serviceBrand: undefined;665value: 1;666}667class AImpl implements A {668_serviceBrand: undefined;669value: 1 = 1;670dispose() {671disposedA = true;672}673}674675const B = createDecorator<B>('B');676interface B {677_serviceBrand: undefined;678value: 1;679}680class BImpl implements B {681_serviceBrand: undefined;682value: 1 = 1;683dispose() {684disposedB = true;685}686}687688const insta = new InstantiationService(new ServiceCollection(689[A, new SyncDescriptor(AImpl, undefined, true)],690[B, new BImpl()],691), true, undefined, true);692693class Consumer {694constructor(695@A public readonly a: A,696@B public readonly b: B697) {698assert.strictEqual(a.value, b.value);699}700}701702const c: Consumer = insta.createInstance(Consumer);703704insta.dispose();705assert.ok(c);706assert.strictEqual(disposedA, true);707assert.strictEqual(disposedB, false);708});709710test('Disposed service cannot be used anymore', function () {711712713const B = createDecorator<B>('B');714interface B {715_serviceBrand: undefined;716value: 1;717}718class BImpl implements B {719_serviceBrand: undefined;720value: 1 = 1;721}722723const insta = new InstantiationService(new ServiceCollection(724[B, new BImpl()],725), true, undefined, true);726727class Consumer {728constructor(729@B public readonly b: B730) {731assert.strictEqual(b.value, 1);732}733}734735const c: Consumer = insta.createInstance(Consumer);736assert.ok(c);737738insta.dispose();739740assert.throws(() => insta.createInstance(Consumer));741assert.throws(() => insta.invokeFunction(accessor => { }));742assert.throws(() => insta.createChild(new ServiceCollection()));743});744745test('Child does not dispose parent', function () {746747const B = createDecorator<B>('B');748interface B {749_serviceBrand: undefined;750value: 1;751}752class BImpl implements B {753_serviceBrand: undefined;754value: 1 = 1;755}756757const insta1 = new InstantiationService(new ServiceCollection(758[B, new BImpl()],759), true, undefined, true);760761const insta2 = insta1.createChild(new ServiceCollection());762763class Consumer {764constructor(765@B public readonly b: B766) {767assert.strictEqual(b.value, 1);768}769}770771assert.ok(insta1.createInstance(Consumer));772assert.ok(insta2.createInstance(Consumer));773774insta2.dispose();775776assert.ok(insta1.createInstance(Consumer)); // parent NOT disposed by child777assert.throws(() => insta2.createInstance(Consumer));778});779780test('Parent does dispose children', function () {781782const B = createDecorator<B>('B');783interface B {784_serviceBrand: undefined;785value: 1;786}787class BImpl implements B {788_serviceBrand: undefined;789value: 1 = 1;790}791792const insta1 = new InstantiationService(new ServiceCollection(793[B, new BImpl()],794), true, undefined, true);795796const insta2 = insta1.createChild(new ServiceCollection());797798class Consumer {799constructor(800@B public readonly b: B801) {802assert.strictEqual(b.value, 1);803}804}805806assert.ok(insta1.createInstance(Consumer));807assert.ok(insta2.createInstance(Consumer));808809insta1.dispose();810811assert.throws(() => insta2.createInstance(Consumer)); // child is disposed by parent812assert.throws(() => insta1.createInstance(Consumer));813});814815ensureNoDisposablesAreLeakedInTestSuite();816});817818819