Path: blob/main/src/vs/workbench/api/test/common/extHostExtensionActivator.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 { promiseWithResolvers, timeout } from '../../../../base/common/async.js';7import { Mutable } from '../../../../base/common/types.js';8import { URI } from '../../../../base/common/uri.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';10import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';11import { NullLogService } from '../../../../platform/log/common/log.js';12import { ActivatedExtension, EmptyExtension, ExtensionActivationTimes, ExtensionsActivator, IExtensionsActivatorHost } from '../../common/extHostExtensionActivator.js';13import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../../services/extensions/common/extensionDescriptionRegistry.js';14import { ExtensionActivationReason, MissingExtensionDependency } from '../../../services/extensions/common/extensions.js';15import { DisposableStore } from '../../../../base/common/lifecycle.js';1617suite('ExtensionsActivator', () => {1819ensureNoDisposablesAreLeakedInTestSuite();2021const idA = new ExtensionIdentifier(`a`);22const idB = new ExtensionIdentifier(`b`);23const idC = new ExtensionIdentifier(`c`);2425test('calls activate only once with sequential activations', async () => {26const disposables = new DisposableStore();27const host = new SimpleExtensionsActivatorHost();28const activator = createActivator(host, [29desc(idA)30], [], disposables);3132await activator.activateByEvent('*', false);33assert.deepStrictEqual(host.activateCalls, [idA]);3435await activator.activateByEvent('*', false);36assert.deepStrictEqual(host.activateCalls, [idA]);3738disposables.dispose();39});4041test('calls activate only once with parallel activations', async () => {42const disposables = new DisposableStore();43const extActivation = new ExtensionActivationPromiseSource();44const host = new PromiseExtensionsActivatorHost([45[idA, extActivation]46]);47const activator = createActivator(host, [48desc(idA, [], ['evt1', 'evt2'])49], [], disposables);5051const activate1 = activator.activateByEvent('evt1', false);52const activate2 = activator.activateByEvent('evt2', false);5354extActivation.resolve();5556await activate1;57await activate2;5859assert.deepStrictEqual(host.activateCalls, [idA]);6061disposables.dispose();62});6364test('activates dependencies first', async () => {65const disposables = new DisposableStore();66const extActivationA = new ExtensionActivationPromiseSource();67const extActivationB = new ExtensionActivationPromiseSource();68const host = new PromiseExtensionsActivatorHost([69[idA, extActivationA],70[idB, extActivationB]71]);72const activator = createActivator(host, [73desc(idA, [idB], ['evt1']),74desc(idB, [], ['evt1']),75], [], disposables);7677const activate = activator.activateByEvent('evt1', false);7879await timeout(0);80assert.deepStrictEqual(host.activateCalls, [idB]);81extActivationB.resolve();8283await timeout(0);84assert.deepStrictEqual(host.activateCalls, [idB, idA]);85extActivationA.resolve();8687await timeout(0);88await activate;8990assert.deepStrictEqual(host.activateCalls, [idB, idA]);9192disposables.dispose();93});9495test('Supports having resolved extensions', async () => {96const disposables = new DisposableStore();97const host = new SimpleExtensionsActivatorHost();98const bExt = desc(idB);99delete (<Mutable<IExtensionDescription>>bExt).main;100delete (<Mutable<IExtensionDescription>>bExt).browser;101const activator = createActivator(host, [102desc(idA, [idB])103], [bExt], disposables);104105await activator.activateByEvent('*', false);106assert.deepStrictEqual(host.activateCalls, [idA]);107108disposables.dispose();109});110111test('Supports having external extensions', async () => {112const disposables = new DisposableStore();113const extActivationA = new ExtensionActivationPromiseSource();114const extActivationB = new ExtensionActivationPromiseSource();115const host = new PromiseExtensionsActivatorHost([116[idA, extActivationA],117[idB, extActivationB]118]);119const bExt = desc(idB);120(<Mutable<IExtensionDescription>>bExt).api = 'none';121const activator = createActivator(host, [122desc(idA, [idB])123], [bExt], disposables);124125const activate = activator.activateByEvent('*', false);126127await timeout(0);128assert.deepStrictEqual(host.activateCalls, [idB]);129extActivationB.resolve();130131await timeout(0);132assert.deepStrictEqual(host.activateCalls, [idB, idA]);133extActivationA.resolve();134135await activate;136assert.deepStrictEqual(host.activateCalls, [idB, idA]);137138disposables.dispose();139});140141test('Error: activateById with missing extension', async () => {142const disposables = new DisposableStore();143const host = new SimpleExtensionsActivatorHost();144const activator = createActivator(host, [145desc(idA),146desc(idB),147], [], disposables);148149let error: Error | undefined = undefined;150try {151await activator.activateById(idC, { startup: false, extensionId: idC, activationEvent: 'none' });152} catch (err) {153error = err;154}155156assert.strictEqual(typeof error === 'undefined', false);157158disposables.dispose();159});160161test('Error: dependency missing', async () => {162const disposables = new DisposableStore();163const host = new SimpleExtensionsActivatorHost();164const activator = createActivator(host, [165desc(idA, [idB]),166], [], disposables);167168await activator.activateByEvent('*', false);169170assert.deepStrictEqual(host.errors.length, 1);171assert.deepStrictEqual(host.errors[0][0], idA);172173disposables.dispose();174});175176test('Error: dependency activation failed', async () => {177const disposables = new DisposableStore();178const extActivationA = new ExtensionActivationPromiseSource();179const extActivationB = new ExtensionActivationPromiseSource();180const host = new PromiseExtensionsActivatorHost([181[idA, extActivationA],182[idB, extActivationB]183]);184const activator = createActivator(host, [185desc(idA, [idB]),186desc(idB)187], [], disposables);188189const activate = activator.activateByEvent('*', false);190extActivationB.reject(new Error(`b fails!`));191192await activate;193assert.deepStrictEqual(host.errors.length, 2);194assert.deepStrictEqual(host.errors[0][0], idB);195assert.deepStrictEqual(host.errors[1][0], idA);196197disposables.dispose();198});199200test('issue #144518: Problem with git extension and vscode-icons', async () => {201const disposables = new DisposableStore();202const extActivationA = new ExtensionActivationPromiseSource();203const extActivationB = new ExtensionActivationPromiseSource();204const extActivationC = new ExtensionActivationPromiseSource();205const host = new PromiseExtensionsActivatorHost([206[idA, extActivationA],207[idB, extActivationB],208[idC, extActivationC]209]);210const activator = createActivator(host, [211desc(idA, [idB]),212desc(idB),213desc(idC),214], [], disposables);215216activator.activateByEvent('*', false);217assert.deepStrictEqual(host.activateCalls, [idB, idC]);218219extActivationB.resolve();220await timeout(0);221222assert.deepStrictEqual(host.activateCalls, [idB, idC, idA]);223extActivationA.resolve();224225disposables.dispose();226});227228class SimpleExtensionsActivatorHost implements IExtensionsActivatorHost {229public readonly activateCalls: ExtensionIdentifier[] = [];230public readonly errors: [ExtensionIdentifier, Error | null, MissingExtensionDependency | null][] = [];231232onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void {233this.errors.push([extensionId, error, missingExtensionDependency]);234}235236actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {237this.activateCalls.push(extensionId);238return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));239}240}241242class PromiseExtensionsActivatorHost extends SimpleExtensionsActivatorHost {243244constructor(245private readonly _promises: [ExtensionIdentifier, ExtensionActivationPromiseSource][]246) {247super();248}249250override actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {251this.activateCalls.push(extensionId);252for (const [id, promiseSource] of this._promises) {253if (id.value === extensionId.value) {254return promiseSource.promise;255}256}257throw new Error(`Unexpected!`);258}259}260261class ExtensionActivationPromiseSource {262private readonly _resolve: (value: ActivatedExtension) => void;263private readonly _reject: (err: Error) => void;264public readonly promise: Promise<ActivatedExtension>;265266constructor() {267({ promise: this.promise, resolve: this._resolve, reject: this._reject } = promiseWithResolvers<ActivatedExtension>());268}269270public resolve(): void {271this._resolve(new EmptyExtension(ExtensionActivationTimes.NONE));272}273274public reject(err: Error): void {275this._reject(err);276}277}278279const basicActivationEventsReader: IActivationEventsReader = {280readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {281return extensionDescription.activationEvents ?? [];282}283};284285function createActivator(host: IExtensionsActivatorHost, extensionDescriptions: IExtensionDescription[], otherHostExtensionDescriptions: IExtensionDescription[] = [], disposables: DisposableStore): ExtensionsActivator {286const registry = disposables.add(new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions));287const globalRegistry = disposables.add(new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions.concat(otherHostExtensionDescriptions)));288return disposables.add(new ExtensionsActivator(registry, globalRegistry, host, new NullLogService()));289}290291function desc(id: ExtensionIdentifier, deps: ExtensionIdentifier[] = [], activationEvents: string[] = ['*']): IExtensionDescription {292return {293name: id.value,294publisher: 'test',295version: '0.0.0',296engines: { vscode: '^1.0.0' },297identifier: id,298extensionLocation: URI.parse(`nothing://nowhere`),299isBuiltin: false,300isUnderDevelopment: false,301isUserBuiltin: false,302activationEvents,303main: 'index.js',304targetPlatform: TargetPlatform.UNDEFINED,305extensionDependencies: deps.map(d => d.value),306enabledApiProposals: undefined,307preRelease: false,308};309}310311});312313314