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