Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/codeMapper/extHostExtensionActivator.test.ts
13399 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 { promiseWithResolvers, timeout } from '../../../../base/common/async.js';
8
import { Mutable } from '../../../../base/common/types.js';
9
import { URI } from '../../../../base/common/uri.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
11
import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
12
import { NullLogService } from '../../../../platform/log/common/log.js';
13
import { ActivatedExtension, EmptyExtension, ExtensionActivationTimes, ExtensionsActivator, IExtensionsActivatorHost } from '../../common/extHostExtensionActivator.js';
14
import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../../services/extensions/common/extensionDescriptionRegistry.js';
15
import { ExtensionActivationReason, MissingExtensionDependency } from '../../../services/extensions/common/extensions.js';
16
17
suite('ExtensionsActivator', () => {
18
19
ensureNoDisposablesAreLeakedInTestSuite();
20
21
const idA = new ExtensionIdentifier(`a`);
22
const idB = new ExtensionIdentifier(`b`);
23
const idC = new ExtensionIdentifier(`c`);
24
25
test('calls activate only once with sequential activations', async () => {
26
const host = new SimpleExtensionsActivatorHost();
27
const activator = createActivator(host, [
28
desc(idA)
29
]);
30
31
await activator.activateByEvent('*', false);
32
assert.deepStrictEqual(host.activateCalls, [idA]);
33
34
await activator.activateByEvent('*', false);
35
assert.deepStrictEqual(host.activateCalls, [idA]);
36
});
37
38
test('calls activate only once with parallel activations', async () => {
39
const extActivation = new ExtensionActivationPromiseSource();
40
const host = new PromiseExtensionsActivatorHost([
41
[idA, extActivation]
42
]);
43
const activator = createActivator(host, [
44
desc(idA, [], ['evt1', 'evt2'])
45
]);
46
47
const activate1 = activator.activateByEvent('evt1', false);
48
const activate2 = activator.activateByEvent('evt2', false);
49
50
extActivation.resolve();
51
52
await activate1;
53
await activate2;
54
55
assert.deepStrictEqual(host.activateCalls, [idA]);
56
});
57
58
test('activates dependencies first', async () => {
59
const extActivationA = new ExtensionActivationPromiseSource();
60
const extActivationB = new ExtensionActivationPromiseSource();
61
const host = new PromiseExtensionsActivatorHost([
62
[idA, extActivationA],
63
[idB, extActivationB]
64
]);
65
const activator = createActivator(host, [
66
desc(idA, [idB], ['evt1']),
67
desc(idB, [], ['evt1']),
68
]);
69
70
const activate = activator.activateByEvent('evt1', false);
71
72
await timeout(0);
73
assert.deepStrictEqual(host.activateCalls, [idB]);
74
extActivationB.resolve();
75
76
await timeout(0);
77
assert.deepStrictEqual(host.activateCalls, [idB, idA]);
78
extActivationA.resolve();
79
80
await timeout(0);
81
await activate;
82
83
assert.deepStrictEqual(host.activateCalls, [idB, idA]);
84
});
85
86
test('Supports having resolved extensions', async () => {
87
const host = new SimpleExtensionsActivatorHost();
88
const bExt = desc(idB);
89
delete (<Mutable<IExtensionDescription>>bExt).main;
90
delete (<Mutable<IExtensionDescription>>bExt).browser;
91
const activator = createActivator(host, [
92
desc(idA, [idB])
93
], [bExt]);
94
95
await activator.activateByEvent('*', false);
96
assert.deepStrictEqual(host.activateCalls, [idA]);
97
});
98
99
test('Supports having external extensions', async () => {
100
const extActivationA = new ExtensionActivationPromiseSource();
101
const extActivationB = new ExtensionActivationPromiseSource();
102
const host = new PromiseExtensionsActivatorHost([
103
[idA, extActivationA],
104
[idB, extActivationB]
105
]);
106
const bExt = desc(idB);
107
(<Mutable<IExtensionDescription>>bExt).api = 'none';
108
const activator = createActivator(host, [
109
desc(idA, [idB])
110
], [bExt]);
111
112
const activate = activator.activateByEvent('*', false);
113
114
await timeout(0);
115
assert.deepStrictEqual(host.activateCalls, [idB]);
116
extActivationB.resolve();
117
118
await timeout(0);
119
assert.deepStrictEqual(host.activateCalls, [idB, idA]);
120
extActivationA.resolve();
121
122
await activate;
123
assert.deepStrictEqual(host.activateCalls, [idB, idA]);
124
});
125
126
test('Error: activateById with missing extension', async () => {
127
const host = new SimpleExtensionsActivatorHost();
128
const activator = createActivator(host, [
129
desc(idA),
130
desc(idB),
131
]);
132
133
let error: Error | undefined = undefined;
134
try {
135
await activator.activateById(idC, { startup: false, extensionId: idC, activationEvent: 'none' });
136
} catch (err) {
137
error = err;
138
}
139
140
assert.strictEqual(typeof error === 'undefined', false);
141
});
142
143
test('Error: dependency missing', async () => {
144
const host = new SimpleExtensionsActivatorHost();
145
const activator = createActivator(host, [
146
desc(idA, [idB]),
147
]);
148
149
await activator.activateByEvent('*', false);
150
151
assert.deepStrictEqual(host.errors.length, 1);
152
assert.deepStrictEqual(host.errors[0][0], idA);
153
});
154
155
test('Error: dependency activation failed', async () => {
156
const extActivationA = new ExtensionActivationPromiseSource();
157
const extActivationB = new ExtensionActivationPromiseSource();
158
const host = new PromiseExtensionsActivatorHost([
159
[idA, extActivationA],
160
[idB, extActivationB]
161
]);
162
const activator = createActivator(host, [
163
desc(idA, [idB]),
164
desc(idB)
165
]);
166
167
const activate = activator.activateByEvent('*', false);
168
extActivationB.reject(new Error(`b fails!`));
169
170
await activate;
171
assert.deepStrictEqual(host.errors.length, 2);
172
assert.deepStrictEqual(host.errors[0][0], idB);
173
assert.deepStrictEqual(host.errors[1][0], idA);
174
});
175
176
test('issue #144518: Problem with git extension and vscode-icons', async () => {
177
const extActivationA = new ExtensionActivationPromiseSource();
178
const extActivationB = new ExtensionActivationPromiseSource();
179
const extActivationC = new ExtensionActivationPromiseSource();
180
const host = new PromiseExtensionsActivatorHost([
181
[idA, extActivationA],
182
[idB, extActivationB],
183
[idC, extActivationC]
184
]);
185
const activator = createActivator(host, [
186
desc(idA, [idB]),
187
desc(idB),
188
desc(idC),
189
]);
190
191
activator.activateByEvent('*', false);
192
assert.deepStrictEqual(host.activateCalls, [idB, idC]);
193
194
extActivationB.resolve();
195
await timeout(0);
196
197
assert.deepStrictEqual(host.activateCalls, [idB, idC, idA]);
198
extActivationA.resolve();
199
});
200
201
class SimpleExtensionsActivatorHost implements IExtensionsActivatorHost {
202
public readonly activateCalls: ExtensionIdentifier[] = [];
203
public readonly errors: [ExtensionIdentifier, Error | null, MissingExtensionDependency | null][] = [];
204
205
onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void {
206
this.errors.push([extensionId, error, missingExtensionDependency]);
207
}
208
209
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
210
this.activateCalls.push(extensionId);
211
return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
212
}
213
}
214
215
class PromiseExtensionsActivatorHost extends SimpleExtensionsActivatorHost {
216
217
constructor(
218
private readonly _promises: [ExtensionIdentifier, ExtensionActivationPromiseSource][]
219
) {
220
super();
221
}
222
223
override actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
224
this.activateCalls.push(extensionId);
225
for (const [id, promiseSource] of this._promises) {
226
if (id.value === extensionId.value) {
227
return promiseSource.promise;
228
}
229
}
230
throw new Error(`Unexpected!`);
231
}
232
}
233
234
class ExtensionActivationPromiseSource {
235
private readonly _resolve: (value: ActivatedExtension) => void;
236
private readonly _reject: (err: Error) => void;
237
public readonly promise: Promise<ActivatedExtension>;
238
239
constructor() {
240
({ promise: this.promise, resolve: this._resolve, reject: this._reject } = promiseWithResolvers<ActivatedExtension>());
241
}
242
243
public resolve(): void {
244
this._resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
245
}
246
247
public reject(err: Error): void {
248
this._reject(err);
249
}
250
}
251
252
const basicActivationEventsReader: IActivationEventsReader = {
253
readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {
254
return extensionDescription.activationEvents ?? [];
255
}
256
};
257
258
function createActivator(host: IExtensionsActivatorHost, extensionDescriptions: IExtensionDescription[], otherHostExtensionDescriptions: IExtensionDescription[] = []): ExtensionsActivator {
259
const registry = new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions);
260
const globalRegistry = new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions.concat(otherHostExtensionDescriptions));
261
return new ExtensionsActivator(registry, globalRegistry, host, new NullLogService());
262
}
263
264
function desc(id: ExtensionIdentifier, deps: ExtensionIdentifier[] = [], activationEvents: string[] = ['*']): IExtensionDescription {
265
return {
266
name: id.value,
267
publisher: 'test',
268
version: '0.0.0',
269
engines: { vscode: '^1.0.0' },
270
identifier: id,
271
extensionLocation: URI.parse(`nothing://nowhere`),
272
isBuiltin: false,
273
isUnderDevelopment: false,
274
isUserBuiltin: false,
275
activationEvents,
276
main: 'index.js',
277
targetPlatform: TargetPlatform.UNDEFINED,
278
extensionDependencies: deps.map(d => d.value),
279
enabledApiProposals: undefined,
280
preRelease: false,
281
};
282
}
283
284
});
285
286