Path: blob/main/extensions/copilot/test/simulation/fixtures/codeMapper/extHostExtensionActivator.test.ts
13399 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';1516suite('ExtensionsActivator', () => {1718ensureNoDisposablesAreLeakedInTestSuite();1920const idA = new ExtensionIdentifier(`a`);21const idB = new ExtensionIdentifier(`b`);22const idC = new ExtensionIdentifier(`c`);2324test('calls activate only once with sequential activations', async () => {25const host = new SimpleExtensionsActivatorHost();26const activator = createActivator(host, [27desc(idA)28]);2930await activator.activateByEvent('*', false);31assert.deepStrictEqual(host.activateCalls, [idA]);3233await activator.activateByEvent('*', false);34assert.deepStrictEqual(host.activateCalls, [idA]);35});3637test('calls activate only once with parallel activations', async () => {38const extActivation = new ExtensionActivationPromiseSource();39const host = new PromiseExtensionsActivatorHost([40[idA, extActivation]41]);42const activator = createActivator(host, [43desc(idA, [], ['evt1', 'evt2'])44]);4546const activate1 = activator.activateByEvent('evt1', false);47const activate2 = activator.activateByEvent('evt2', false);4849extActivation.resolve();5051await activate1;52await activate2;5354assert.deepStrictEqual(host.activateCalls, [idA]);55});5657test('activates dependencies first', async () => {58const extActivationA = new ExtensionActivationPromiseSource();59const extActivationB = new ExtensionActivationPromiseSource();60const host = new PromiseExtensionsActivatorHost([61[idA, extActivationA],62[idB, extActivationB]63]);64const activator = createActivator(host, [65desc(idA, [idB], ['evt1']),66desc(idB, [], ['evt1']),67]);6869const activate = activator.activateByEvent('evt1', false);7071await timeout(0);72assert.deepStrictEqual(host.activateCalls, [idB]);73extActivationB.resolve();7475await timeout(0);76assert.deepStrictEqual(host.activateCalls, [idB, idA]);77extActivationA.resolve();7879await timeout(0);80await activate;8182assert.deepStrictEqual(host.activateCalls, [idB, idA]);83});8485test('Supports having resolved extensions', async () => {86const host = new SimpleExtensionsActivatorHost();87const bExt = desc(idB);88delete (<Mutable<IExtensionDescription>>bExt).main;89delete (<Mutable<IExtensionDescription>>bExt).browser;90const activator = createActivator(host, [91desc(idA, [idB])92], [bExt]);9394await activator.activateByEvent('*', false);95assert.deepStrictEqual(host.activateCalls, [idA]);96});9798test('Supports having external extensions', async () => {99const extActivationA = new ExtensionActivationPromiseSource();100const extActivationB = new ExtensionActivationPromiseSource();101const host = new PromiseExtensionsActivatorHost([102[idA, extActivationA],103[idB, extActivationB]104]);105const bExt = desc(idB);106(<Mutable<IExtensionDescription>>bExt).api = 'none';107const activator = createActivator(host, [108desc(idA, [idB])109], [bExt]);110111const activate = activator.activateByEvent('*', false);112113await timeout(0);114assert.deepStrictEqual(host.activateCalls, [idB]);115extActivationB.resolve();116117await timeout(0);118assert.deepStrictEqual(host.activateCalls, [idB, idA]);119extActivationA.resolve();120121await activate;122assert.deepStrictEqual(host.activateCalls, [idB, idA]);123});124125test('Error: activateById with missing extension', async () => {126const host = new SimpleExtensionsActivatorHost();127const activator = createActivator(host, [128desc(idA),129desc(idB),130]);131132let error: Error | undefined = undefined;133try {134await activator.activateById(idC, { startup: false, extensionId: idC, activationEvent: 'none' });135} catch (err) {136error = err;137}138139assert.strictEqual(typeof error === 'undefined', false);140});141142test('Error: dependency missing', async () => {143const host = new SimpleExtensionsActivatorHost();144const activator = createActivator(host, [145desc(idA, [idB]),146]);147148await activator.activateByEvent('*', false);149150assert.deepStrictEqual(host.errors.length, 1);151assert.deepStrictEqual(host.errors[0][0], idA);152});153154test('Error: dependency activation failed', async () => {155const extActivationA = new ExtensionActivationPromiseSource();156const extActivationB = new ExtensionActivationPromiseSource();157const host = new PromiseExtensionsActivatorHost([158[idA, extActivationA],159[idB, extActivationB]160]);161const activator = createActivator(host, [162desc(idA, [idB]),163desc(idB)164]);165166const activate = activator.activateByEvent('*', false);167extActivationB.reject(new Error(`b fails!`));168169await activate;170assert.deepStrictEqual(host.errors.length, 2);171assert.deepStrictEqual(host.errors[0][0], idB);172assert.deepStrictEqual(host.errors[1][0], idA);173});174175test('issue #144518: Problem with git extension and vscode-icons', async () => {176const extActivationA = new ExtensionActivationPromiseSource();177const extActivationB = new ExtensionActivationPromiseSource();178const extActivationC = new ExtensionActivationPromiseSource();179const host = new PromiseExtensionsActivatorHost([180[idA, extActivationA],181[idB, extActivationB],182[idC, extActivationC]183]);184const activator = createActivator(host, [185desc(idA, [idB]),186desc(idB),187desc(idC),188]);189190activator.activateByEvent('*', false);191assert.deepStrictEqual(host.activateCalls, [idB, idC]);192193extActivationB.resolve();194await timeout(0);195196assert.deepStrictEqual(host.activateCalls, [idB, idC, idA]);197extActivationA.resolve();198});199200class SimpleExtensionsActivatorHost implements IExtensionsActivatorHost {201public readonly activateCalls: ExtensionIdentifier[] = [];202public readonly errors: [ExtensionIdentifier, Error | null, MissingExtensionDependency | null][] = [];203204onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void {205this.errors.push([extensionId, error, missingExtensionDependency]);206}207208actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {209this.activateCalls.push(extensionId);210return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));211}212}213214class PromiseExtensionsActivatorHost extends SimpleExtensionsActivatorHost {215216constructor(217private readonly _promises: [ExtensionIdentifier, ExtensionActivationPromiseSource][]218) {219super();220}221222override actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> {223this.activateCalls.push(extensionId);224for (const [id, promiseSource] of this._promises) {225if (id.value === extensionId.value) {226return promiseSource.promise;227}228}229throw new Error(`Unexpected!`);230}231}232233class ExtensionActivationPromiseSource {234private readonly _resolve: (value: ActivatedExtension) => void;235private readonly _reject: (err: Error) => void;236public readonly promise: Promise<ActivatedExtension>;237238constructor() {239({ promise: this.promise, resolve: this._resolve, reject: this._reject } = promiseWithResolvers<ActivatedExtension>());240}241242public resolve(): void {243this._resolve(new EmptyExtension(ExtensionActivationTimes.NONE));244}245246public reject(err: Error): void {247this._reject(err);248}249}250251const basicActivationEventsReader: IActivationEventsReader = {252readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {253return extensionDescription.activationEvents ?? [];254}255};256257function createActivator(host: IExtensionsActivatorHost, extensionDescriptions: IExtensionDescription[], otherHostExtensionDescriptions: IExtensionDescription[] = []): ExtensionsActivator {258const registry = new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions);259const globalRegistry = new ExtensionDescriptionRegistry(basicActivationEventsReader, extensionDescriptions.concat(otherHostExtensionDescriptions));260return new ExtensionsActivator(registry, globalRegistry, host, new NullLogService());261}262263function desc(id: ExtensionIdentifier, deps: ExtensionIdentifier[] = [], activationEvents: string[] = ['*']): IExtensionDescription {264return {265name: id.value,266publisher: 'test',267version: '0.0.0',268engines: { vscode: '^1.0.0' },269identifier: id,270extensionLocation: URI.parse(`nothing://nowhere`),271isBuiltin: false,272isUnderDevelopment: false,273isUserBuiltin: false,274activationEvents,275main: 'index.js',276targetPlatform: TargetPlatform.UNDEFINED,277extensionDependencies: deps.map(d => d.value),278enabledApiProposals: undefined,279preRelease: false,280};281}282283});284285286