Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/instantiation/test/common/instantiationService.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, Event } from '../../../../base/common/event.js';
8
import { dispose } from '../../../../base/common/lifecycle.js';
9
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
10
import { SyncDescriptor } from '../../common/descriptors.js';
11
import { createDecorator, IInstantiationService, ServicesAccessor } from '../../common/instantiation.js';
12
import { InstantiationService } from '../../common/instantiationService.js';
13
import { ServiceCollection } from '../../common/serviceCollection.js';
14
15
const IService1 = createDecorator<IService1>('service1');
16
17
interface IService1 {
18
readonly _serviceBrand: undefined;
19
c: number;
20
}
21
22
class Service1 implements IService1 {
23
declare readonly _serviceBrand: undefined;
24
c = 1;
25
}
26
27
const IService2 = createDecorator<IService2>('service2');
28
29
interface IService2 {
30
readonly _serviceBrand: undefined;
31
d: boolean;
32
}
33
34
class Service2 implements IService2 {
35
declare readonly _serviceBrand: undefined;
36
d = true;
37
}
38
39
const IService3 = createDecorator<IService3>('service3');
40
41
interface IService3 {
42
readonly _serviceBrand: undefined;
43
s: string;
44
}
45
46
class Service3 implements IService3 {
47
declare readonly _serviceBrand: undefined;
48
s = 'farboo';
49
}
50
51
const IDependentService = createDecorator<IDependentService>('dependentService');
52
53
interface IDependentService {
54
readonly _serviceBrand: undefined;
55
name: string;
56
}
57
58
class DependentService implements IDependentService {
59
declare readonly _serviceBrand: undefined;
60
constructor(@IService1 service: IService1) {
61
assert.strictEqual(service.c, 1);
62
}
63
64
name = 'farboo';
65
}
66
67
class Service1Consumer {
68
69
constructor(@IService1 service1: IService1) {
70
assert.ok(service1);
71
assert.strictEqual(service1.c, 1);
72
}
73
}
74
75
class Target2Dep {
76
77
constructor(@IService1 service1: IService1, @IService2 service2: Service2) {
78
assert.ok(service1 instanceof Service1);
79
assert.ok(service2 instanceof Service2);
80
}
81
}
82
83
class TargetWithStaticParam {
84
constructor(v: boolean, @IService1 service1: IService1) {
85
assert.ok(v);
86
assert.ok(service1);
87
assert.strictEqual(service1.c, 1);
88
}
89
}
90
91
92
93
class DependentServiceTarget {
94
constructor(@IDependentService d: IDependentService) {
95
assert.ok(d);
96
assert.strictEqual(d.name, 'farboo');
97
}
98
}
99
100
class DependentServiceTarget2 {
101
constructor(@IDependentService d: IDependentService, @IService1 s: IService1) {
102
assert.ok(d);
103
assert.strictEqual(d.name, 'farboo');
104
assert.ok(s);
105
assert.strictEqual(s.c, 1);
106
}
107
}
108
109
110
class ServiceLoop1 implements IService1 {
111
declare readonly _serviceBrand: undefined;
112
c = 1;
113
114
constructor(@IService2 s: IService2) {
115
116
}
117
}
118
119
class ServiceLoop2 implements IService2 {
120
declare readonly _serviceBrand: undefined;
121
d = true;
122
123
constructor(@IService1 s: IService1) {
124
125
}
126
}
127
128
suite('Instantiation Service', () => {
129
130
test('service collection, cannot overwrite', function () {
131
const collection = new ServiceCollection();
132
let result = collection.set(IService1, null!);
133
assert.strictEqual(result, undefined);
134
result = collection.set(IService1, new Service1());
135
assert.strictEqual(result, null);
136
});
137
138
test('service collection, add/has', function () {
139
const collection = new ServiceCollection();
140
collection.set(IService1, null!);
141
assert.ok(collection.has(IService1));
142
143
collection.set(IService2, null!);
144
assert.ok(collection.has(IService1));
145
assert.ok(collection.has(IService2));
146
});
147
148
test('@Param - simple clase', function () {
149
const collection = new ServiceCollection();
150
const service = new InstantiationService(collection);
151
collection.set(IService1, new Service1());
152
collection.set(IService2, new Service2());
153
collection.set(IService3, new Service3());
154
155
service.createInstance(Service1Consumer);
156
});
157
158
test('@Param - fixed args', function () {
159
const collection = new ServiceCollection();
160
const service = new InstantiationService(collection);
161
collection.set(IService1, new Service1());
162
collection.set(IService2, new Service2());
163
collection.set(IService3, new Service3());
164
165
service.createInstance(TargetWithStaticParam, true);
166
});
167
168
test('service collection is live', function () {
169
170
const collection = new ServiceCollection();
171
collection.set(IService1, new Service1());
172
173
const service = new InstantiationService(collection);
174
service.createInstance(Service1Consumer);
175
176
collection.set(IService2, new Service2());
177
178
service.createInstance(Target2Dep);
179
service.invokeFunction(function (a) {
180
assert.ok(a.get(IService1));
181
assert.ok(a.get(IService2));
182
});
183
});
184
185
// we made this a warning
186
// test('@Param - too many args', function () {
187
// let service = instantiationService.create(Object.create(null));
188
// service.addSingleton(IService1, new Service1());
189
// service.addSingleton(IService2, new Service2());
190
// service.addSingleton(IService3, new Service3());
191
192
// assert.throws(() => service.createInstance(ParameterTarget2, true, 2));
193
// });
194
195
// test('@Param - too few args', function () {
196
// let service = instantiationService.create(Object.create(null));
197
// service.addSingleton(IService1, new Service1());
198
// service.addSingleton(IService2, new Service2());
199
// service.addSingleton(IService3, new Service3());
200
201
// assert.throws(() => service.createInstance(ParameterTarget2));
202
// });
203
204
test('SyncDesc - no dependencies', function () {
205
const collection = new ServiceCollection();
206
const service = new InstantiationService(collection);
207
collection.set(IService1, new SyncDescriptor<IService1>(Service1));
208
209
service.invokeFunction(accessor => {
210
211
const service1 = accessor.get(IService1);
212
assert.ok(service1);
213
assert.strictEqual(service1.c, 1);
214
215
const service2 = accessor.get(IService1);
216
assert.ok(service1 === service2);
217
});
218
});
219
220
test('SyncDesc - service with service dependency', function () {
221
const collection = new ServiceCollection();
222
const service = new InstantiationService(collection);
223
collection.set(IService1, new SyncDescriptor<IService1>(Service1));
224
collection.set(IDependentService, new SyncDescriptor<IDependentService>(DependentService));
225
226
service.invokeFunction(accessor => {
227
const d = accessor.get(IDependentService);
228
assert.ok(d);
229
assert.strictEqual(d.name, 'farboo');
230
});
231
});
232
233
test('SyncDesc - target depends on service future', function () {
234
const collection = new ServiceCollection();
235
const service = new InstantiationService(collection);
236
collection.set(IService1, new SyncDescriptor<IService1>(Service1));
237
collection.set(IDependentService, new SyncDescriptor<IDependentService>(DependentService));
238
239
const d = service.createInstance(DependentServiceTarget);
240
assert.ok(d instanceof DependentServiceTarget);
241
242
const d2 = service.createInstance(DependentServiceTarget2);
243
assert.ok(d2 instanceof DependentServiceTarget2);
244
});
245
246
test('SyncDesc - explode on loop', function () {
247
const collection = new ServiceCollection();
248
const service = new InstantiationService(collection);
249
collection.set(IService1, new SyncDescriptor<IService1>(ServiceLoop1));
250
collection.set(IService2, new SyncDescriptor<IService2>(ServiceLoop2));
251
252
assert.throws(() => {
253
service.invokeFunction(accessor => {
254
accessor.get(IService1);
255
});
256
});
257
assert.throws(() => {
258
service.invokeFunction(accessor => {
259
accessor.get(IService2);
260
});
261
});
262
263
try {
264
service.invokeFunction(accessor => {
265
accessor.get(IService1);
266
});
267
} catch (err) {
268
assert.ok(err.name);
269
assert.ok(err.message);
270
}
271
});
272
273
test('Invoke - get services', function () {
274
const collection = new ServiceCollection();
275
const service = new InstantiationService(collection);
276
collection.set(IService1, new Service1());
277
collection.set(IService2, new Service2());
278
279
function test(accessor: ServicesAccessor) {
280
assert.ok(accessor.get(IService1) instanceof Service1);
281
assert.strictEqual(accessor.get(IService1).c, 1);
282
283
return true;
284
}
285
286
assert.strictEqual(service.invokeFunction(test), true);
287
});
288
289
test('Invoke - get service, optional', function () {
290
const collection = new ServiceCollection([IService1, new Service1()]);
291
const service = new InstantiationService(collection);
292
293
function test(accessor: ServicesAccessor) {
294
assert.ok(accessor.get(IService1) instanceof Service1);
295
assert.throws(() => accessor.get(IService2));
296
return true;
297
}
298
assert.strictEqual(service.invokeFunction(test), true);
299
});
300
301
test('Invoke - keeping accessor NOT allowed', function () {
302
const collection = new ServiceCollection();
303
const service = new InstantiationService(collection);
304
collection.set(IService1, new Service1());
305
collection.set(IService2, new Service2());
306
307
let cached: ServicesAccessor;
308
309
function test(accessor: ServicesAccessor) {
310
assert.ok(accessor.get(IService1) instanceof Service1);
311
assert.strictEqual(accessor.get(IService1).c, 1);
312
cached = accessor;
313
return true;
314
}
315
316
assert.strictEqual(service.invokeFunction(test), true);
317
318
assert.throws(() => cached.get(IService2));
319
});
320
321
test('Invoke - throw error', function () {
322
const collection = new ServiceCollection();
323
const service = new InstantiationService(collection);
324
collection.set(IService1, new Service1());
325
collection.set(IService2, new Service2());
326
327
function test(accessor: ServicesAccessor) {
328
throw new Error();
329
}
330
331
assert.throws(() => service.invokeFunction(test));
332
});
333
334
test('Create child', function () {
335
336
let serviceInstanceCount = 0;
337
338
const CtorCounter = class implements Service1 {
339
declare readonly _serviceBrand: undefined;
340
c = 1;
341
constructor() {
342
serviceInstanceCount += 1;
343
}
344
};
345
346
// creating the service instance BEFORE the child service
347
let service = new InstantiationService(new ServiceCollection([IService1, new SyncDescriptor(CtorCounter)]));
348
service.createInstance(Service1Consumer);
349
350
// second instance must be earlier ONE
351
let child = service.createChild(new ServiceCollection([IService2, new Service2()]));
352
child.createInstance(Service1Consumer);
353
354
assert.strictEqual(serviceInstanceCount, 1);
355
356
// creating the service instance AFTER the child service
357
serviceInstanceCount = 0;
358
service = new InstantiationService(new ServiceCollection([IService1, new SyncDescriptor(CtorCounter)]));
359
child = service.createChild(new ServiceCollection([IService2, new Service2()]));
360
361
// second instance must be earlier ONE
362
service.createInstance(Service1Consumer);
363
child.createInstance(Service1Consumer);
364
365
assert.strictEqual(serviceInstanceCount, 1);
366
});
367
368
test('Remote window / integration tests is broken #105562', function () {
369
370
const Service1 = createDecorator<any>('service1');
371
class Service1Impl {
372
constructor(@IInstantiationService insta: IInstantiationService) {
373
const c = insta.invokeFunction(accessor => accessor.get(Service2)); // THIS is the recursive call
374
assert.ok(c);
375
}
376
}
377
const Service2 = createDecorator<any>('service2');
378
class Service2Impl {
379
constructor() { }
380
}
381
382
// This service depends on Service1 and Service2 BUT creating Service1 creates Service2 (via recursive invocation)
383
// and then Servce2 should not be created a second time
384
const Service21 = createDecorator<any>('service21');
385
class Service21Impl {
386
constructor(@Service2 public readonly service2: Service2Impl, @Service1 public readonly service1: Service1Impl) { }
387
}
388
389
const insta = new InstantiationService(new ServiceCollection(
390
[Service1, new SyncDescriptor(Service1Impl)],
391
[Service2, new SyncDescriptor(Service2Impl)],
392
[Service21, new SyncDescriptor(Service21Impl)],
393
));
394
395
const obj = insta.invokeFunction(accessor => accessor.get(Service21));
396
assert.ok(obj);
397
});
398
399
test('Sync/Async dependency loop', async function () {
400
401
const A = createDecorator<A>('A');
402
const B = createDecorator<B>('B');
403
interface A { _serviceBrand: undefined; doIt(): void }
404
interface B { _serviceBrand: undefined; b(): boolean }
405
406
class BConsumer {
407
constructor(@B private readonly b: B) {
408
409
}
410
doIt() {
411
return this.b.b();
412
}
413
}
414
415
class AService implements A {
416
_serviceBrand: undefined;
417
prop: BConsumer;
418
constructor(@IInstantiationService insta: IInstantiationService) {
419
this.prop = insta.createInstance(BConsumer);
420
}
421
doIt() {
422
return this.prop.doIt();
423
}
424
}
425
426
class BService implements B {
427
_serviceBrand: undefined;
428
constructor(@A a: A) {
429
assert.ok(a);
430
}
431
b() { return true; }
432
}
433
434
// SYNC -> explodes AImpl -> [insta:BConsumer] -> BImpl -> AImpl
435
{
436
const insta1 = new InstantiationService(new ServiceCollection(
437
[A, new SyncDescriptor(AService)],
438
[B, new SyncDescriptor(BService)],
439
), true, undefined, true);
440
441
try {
442
insta1.invokeFunction(accessor => accessor.get(A));
443
assert.ok(false);
444
445
} catch (error) {
446
assert.ok(error instanceof Error);
447
assert.ok(error.message.includes('RECURSIVELY'));
448
}
449
}
450
451
// ASYNC -> doesn't explode but cycle is tracked
452
{
453
const insta2 = new InstantiationService(new ServiceCollection(
454
[A, new SyncDescriptor(AService, undefined, true)],
455
[B, new SyncDescriptor(BService, undefined)],
456
), true, undefined, true);
457
458
const a = insta2.invokeFunction(accessor => accessor.get(A));
459
a.doIt();
460
461
const cycle = insta2._globalGraph?.findCycleSlow();
462
assert.strictEqual(cycle, 'A -> B -> A');
463
}
464
});
465
466
test('Delayed and events', function () {
467
const A = createDecorator<A>('A');
468
interface A {
469
_serviceBrand: undefined;
470
onDidDoIt: Event<any>;
471
doIt(): void;
472
}
473
474
let created = false;
475
class AImpl implements A {
476
_serviceBrand: undefined;
477
_doIt = 0;
478
479
_onDidDoIt = new Emitter<this>();
480
onDidDoIt: Event<this> = this._onDidDoIt.event;
481
482
constructor() {
483
created = true;
484
}
485
486
doIt(): void {
487
this._doIt += 1;
488
this._onDidDoIt.fire(this);
489
}
490
}
491
492
const insta = new InstantiationService(new ServiceCollection(
493
[A, new SyncDescriptor(AImpl, undefined, true)],
494
), true, undefined, true);
495
496
class Consumer {
497
constructor(@A public readonly a: A) {
498
// eager subscribe -> NO service instance
499
}
500
}
501
502
const c: Consumer = insta.createInstance(Consumer);
503
let eventCount = 0;
504
505
// subscribing to event doesn't trigger instantiation
506
const listener = (e: any) => {
507
assert.ok(e instanceof AImpl);
508
eventCount++;
509
};
510
const d1 = c.a.onDidDoIt(listener);
511
const d2 = c.a.onDidDoIt(listener);
512
assert.strictEqual(created, false);
513
assert.strictEqual(eventCount, 0);
514
d2.dispose();
515
516
// instantiation happens on first call
517
c.a.doIt();
518
assert.strictEqual(created, true);
519
assert.strictEqual(eventCount, 1);
520
521
522
const d3 = c.a.onDidDoIt(listener);
523
c.a.doIt();
524
assert.strictEqual(eventCount, 3);
525
526
dispose([d1, d3]);
527
});
528
529
530
test('Capture event before init, use after init', function () {
531
const A = createDecorator<A>('A');
532
interface A {
533
_serviceBrand: undefined;
534
onDidDoIt: Event<any>;
535
doIt(): void;
536
noop(): void;
537
}
538
539
let created = false;
540
class AImpl implements A {
541
_serviceBrand: undefined;
542
_doIt = 0;
543
544
_onDidDoIt = new Emitter<this>();
545
onDidDoIt: Event<this> = this._onDidDoIt.event;
546
547
constructor() {
548
created = true;
549
}
550
551
doIt(): void {
552
this._doIt += 1;
553
this._onDidDoIt.fire(this);
554
}
555
556
noop(): void {
557
}
558
}
559
560
const insta = new InstantiationService(new ServiceCollection(
561
[A, new SyncDescriptor(AImpl, undefined, true)],
562
), true, undefined, true);
563
564
class Consumer {
565
constructor(@A public readonly a: A) {
566
// eager subscribe -> NO service instance
567
}
568
}
569
570
const c: Consumer = insta.createInstance(Consumer);
571
let eventCount = 0;
572
573
// subscribing to event doesn't trigger instantiation
574
const listener = (e: any) => {
575
assert.ok(e instanceof AImpl);
576
eventCount++;
577
};
578
579
const event = c.a.onDidDoIt;
580
581
// const d1 = c.a.onDidDoIt(listener);
582
assert.strictEqual(created, false);
583
584
c.a.noop();
585
assert.strictEqual(created, true);
586
587
const d1 = event(listener);
588
589
c.a.doIt();
590
591
592
// instantiation happens on first call
593
assert.strictEqual(eventCount, 1);
594
595
dispose(d1);
596
});
597
598
test('Dispose early event listener', function () {
599
const A = createDecorator<A>('A');
600
interface A {
601
_serviceBrand: undefined;
602
onDidDoIt: Event<any>;
603
doIt(): void;
604
}
605
let created = false;
606
class AImpl implements A {
607
_serviceBrand: undefined;
608
_doIt = 0;
609
610
_onDidDoIt = new Emitter<this>();
611
onDidDoIt: Event<this> = this._onDidDoIt.event;
612
613
constructor() {
614
created = true;
615
}
616
617
doIt(): void {
618
this._doIt += 1;
619
this._onDidDoIt.fire(this);
620
}
621
}
622
623
const insta = new InstantiationService(new ServiceCollection(
624
[A, new SyncDescriptor(AImpl, undefined, true)],
625
), true, undefined, true);
626
627
class Consumer {
628
constructor(@A public readonly a: A) {
629
// eager subscribe -> NO service instance
630
}
631
}
632
633
const c: Consumer = insta.createInstance(Consumer);
634
let eventCount = 0;
635
636
// subscribing to event doesn't trigger instantiation
637
const listener = (e: any) => {
638
assert.ok(e instanceof AImpl);
639
eventCount++;
640
};
641
642
const d1 = c.a.onDidDoIt(listener);
643
assert.strictEqual(created, false);
644
assert.strictEqual(eventCount, 0);
645
646
c.a.doIt();
647
648
// instantiation happens on first call
649
assert.strictEqual(created, true);
650
assert.strictEqual(eventCount, 1);
651
652
dispose(d1);
653
654
c.a.doIt();
655
assert.strictEqual(eventCount, 1);
656
});
657
658
659
test('Dispose services it created', function () {
660
let disposedA = false;
661
let disposedB = false;
662
663
const A = createDecorator<A>('A');
664
interface A {
665
_serviceBrand: undefined;
666
value: 1;
667
}
668
class AImpl implements A {
669
_serviceBrand: undefined;
670
value: 1 = 1;
671
dispose() {
672
disposedA = true;
673
}
674
}
675
676
const B = createDecorator<B>('B');
677
interface B {
678
_serviceBrand: undefined;
679
value: 1;
680
}
681
class BImpl implements B {
682
_serviceBrand: undefined;
683
value: 1 = 1;
684
dispose() {
685
disposedB = true;
686
}
687
}
688
689
const insta = new InstantiationService(new ServiceCollection(
690
[A, new SyncDescriptor(AImpl, undefined, true)],
691
[B, new BImpl()],
692
), true, undefined, true);
693
694
class Consumer {
695
constructor(
696
@A public readonly a: A,
697
@B public readonly b: B
698
) {
699
assert.strictEqual(a.value, b.value);
700
}
701
}
702
703
const c: Consumer = insta.createInstance(Consumer);
704
705
insta.dispose();
706
assert.ok(c);
707
assert.strictEqual(disposedA, true);
708
assert.strictEqual(disposedB, false);
709
});
710
711
test('Disposed service cannot be used anymore', function () {
712
713
714
const B = createDecorator<B>('B');
715
interface B {
716
_serviceBrand: undefined;
717
value: 1;
718
}
719
class BImpl implements B {
720
_serviceBrand: undefined;
721
value: 1 = 1;
722
}
723
724
const insta = new InstantiationService(new ServiceCollection(
725
[B, new BImpl()],
726
), true, undefined, true);
727
728
class Consumer {
729
constructor(
730
@B public readonly b: B
731
) {
732
assert.strictEqual(b.value, 1);
733
}
734
}
735
736
const c: Consumer = insta.createInstance(Consumer);
737
assert.ok(c);
738
739
insta.dispose();
740
741
assert.throws(() => insta.createInstance(Consumer));
742
assert.throws(() => insta.invokeFunction(accessor => { }));
743
assert.throws(() => insta.createChild(new ServiceCollection()));
744
});
745
746
test('Child does not dispose parent', function () {
747
748
const B = createDecorator<B>('B');
749
interface B {
750
_serviceBrand: undefined;
751
value: 1;
752
}
753
class BImpl implements B {
754
_serviceBrand: undefined;
755
value: 1 = 1;
756
}
757
758
const insta1 = new InstantiationService(new ServiceCollection(
759
[B, new BImpl()],
760
), true, undefined, true);
761
762
const insta2 = insta1.createChild(new ServiceCollection());
763
764
class Consumer {
765
constructor(
766
@B public readonly b: B
767
) {
768
assert.strictEqual(b.value, 1);
769
}
770
}
771
772
assert.ok(insta1.createInstance(Consumer));
773
assert.ok(insta2.createInstance(Consumer));
774
775
insta2.dispose();
776
777
assert.ok(insta1.createInstance(Consumer)); // parent NOT disposed by child
778
assert.throws(() => insta2.createInstance(Consumer));
779
});
780
781
test('Parent does dispose children', function () {
782
783
const B = createDecorator<B>('B');
784
interface B {
785
_serviceBrand: undefined;
786
value: 1;
787
}
788
class BImpl implements B {
789
_serviceBrand: undefined;
790
value: 1 = 1;
791
}
792
793
const insta1 = new InstantiationService(new ServiceCollection(
794
[B, new BImpl()],
795
), true, undefined, true);
796
797
const insta2 = insta1.createChild(new ServiceCollection());
798
799
class Consumer {
800
constructor(
801
@B public readonly b: B
802
) {
803
assert.strictEqual(b.value, 1);
804
}
805
}
806
807
assert.ok(insta1.createInstance(Consumer));
808
assert.ok(insta2.createInstance(Consumer));
809
810
insta1.dispose();
811
812
assert.throws(() => insta2.createInstance(Consumer)); // child is disposed by parent
813
assert.throws(() => insta1.createInstance(Consumer));
814
});
815
816
ensureNoDisposablesAreLeakedInTestSuite();
817
});
818
819