Path: blob/main/src/vs/platform/extensionManagement/test/node/extensionsScannerService.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*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { VSBuffer } from '../../../../base/common/buffer.js';6import { dirname, joinPath } from '../../../../base/common/resources.js';7import { URI } from '../../../../base/common/uri.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';9import { INativeEnvironmentService } from '../../../environment/common/environment.js';10import { IExtensionsProfileScannerService, IProfileExtensionsScanOptions } from '../../common/extensionsProfileScannerService.js';11import { AbstractExtensionsScannerService, ExtensionScannerInput, IExtensionsScannerService, IScannedExtensionManifest, Translations } from '../../common/extensionsScannerService.js';12import { ExtensionsProfileScannerService } from '../../node/extensionsProfileScannerService.js';13import { ExtensionType, IExtensionManifest, TargetPlatform } from '../../../extensions/common/extensions.js';14import { IFileService } from '../../../files/common/files.js';15import { FileService } from '../../../files/common/fileService.js';16import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js';17import { IInstantiationService } from '../../../instantiation/common/instantiation.js';18import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js';19import { ILogService, NullLogService } from '../../../log/common/log.js';20import { IProductService } from '../../../product/common/productService.js';21import { IUriIdentityService } from '../../../uriIdentity/common/uriIdentity.js';22import { UriIdentityService } from '../../../uriIdentity/common/uriIdentityService.js';23import { IUserDataProfilesService, UserDataProfilesService } from '../../../userDataProfile/common/userDataProfile.js';2425let translations: Translations = Object.create(null);26const ROOT = URI.file('/ROOT');2728class ExtensionsScannerService extends AbstractExtensionsScannerService implements IExtensionsScannerService {2930constructor(31@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,32@IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService,33@IFileService fileService: IFileService,34@ILogService logService: ILogService,35@INativeEnvironmentService nativeEnvironmentService: INativeEnvironmentService,36@IProductService productService: IProductService,37@IUriIdentityService uriIdentityService: IUriIdentityService,38@IInstantiationService instantiationService: IInstantiationService,39) {40super(41URI.file(nativeEnvironmentService.builtinExtensionsPath),42URI.file(nativeEnvironmentService.extensionsPath),43joinPath(nativeEnvironmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json'),44userDataProfilesService.defaultProfile,45userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService, uriIdentityService, instantiationService);46}4748protected async getTranslations(language: string): Promise<Translations> {49return translations;50}5152}5354suite('NativeExtensionsScanerService Test', () => {5556const disposables = ensureNoDisposablesAreLeakedInTestSuite();57let instantiationService: TestInstantiationService;5859setup(async () => {60translations = {};61instantiationService = disposables.add(new TestInstantiationService());62const logService = new NullLogService();63const fileService = disposables.add(new FileService(logService));64const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());65disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));66instantiationService.stub(ILogService, logService);67instantiationService.stub(IFileService, fileService);68const systemExtensionsLocation = joinPath(ROOT, 'system');69const userExtensionsLocation = joinPath(ROOT, 'extensions');70const environmentService = instantiationService.stub(INativeEnvironmentService, {71userHome: ROOT,72userRoamingDataHome: ROOT,73builtinExtensionsPath: systemExtensionsLocation.fsPath,74extensionsPath: userExtensionsLocation.fsPath,75cacheHome: joinPath(ROOT, 'cache'),76});77instantiationService.stub(IProductService, { version: '1.66.0' });78const uriIdentityService = disposables.add(new UriIdentityService(fileService));79instantiationService.stub(IUriIdentityService, uriIdentityService);80const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));81instantiationService.stub(IUserDataProfilesService, userDataProfilesService);82instantiationService.stub(IExtensionsProfileScannerService, disposables.add(new ExtensionsProfileScannerService(environmentService, fileService, userDataProfilesService, uriIdentityService, logService)));83await fileService.createFolder(systemExtensionsLocation);84await fileService.createFolder(userExtensionsLocation);85});8687test('scan system extension', async () => {88const manifest: Partial<IExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' });89const extensionLocation = await aSystemExtension(manifest);90const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));9192const actual = await testObject.scanSystemExtensions({});9394assert.deepStrictEqual(actual.length, 1);95assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });96assert.deepStrictEqual(actual[0].location.toString(), extensionLocation.toString());97assert.deepStrictEqual(actual[0].isBuiltin, true);98assert.deepStrictEqual(actual[0].type, ExtensionType.System);99assert.deepStrictEqual(actual[0].isValid, true);100assert.deepStrictEqual(actual[0].validations, []);101assert.deepStrictEqual(actual[0].metadata, undefined);102assert.deepStrictEqual(actual[0].targetPlatform, TargetPlatform.UNDEFINED);103assert.deepStrictEqual(actual[0].manifest, manifest);104});105106test('scan user extensions', async () => {107const manifest: Partial<IScannedExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' });108const extensionLocation = await aUserExtension(manifest);109const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));110111const actual = await testObject.scanAllUserExtensions();112113assert.deepStrictEqual(actual.length, 1);114assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });115assert.deepStrictEqual(actual[0].location.toString(), extensionLocation.toString());116assert.deepStrictEqual(actual[0].isBuiltin, false);117assert.deepStrictEqual(actual[0].type, ExtensionType.User);118assert.deepStrictEqual(actual[0].isValid, true);119assert.deepStrictEqual(actual[0].validations, []);120assert.deepStrictEqual(actual[0].metadata, undefined);121assert.deepStrictEqual(actual[0].targetPlatform, TargetPlatform.UNDEFINED);122delete manifest.__metadata;123assert.deepStrictEqual(actual[0].manifest, manifest);124});125126test('scan existing extension', async () => {127const manifest: Partial<IExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' });128const extensionLocation = await aUserExtension(manifest);129const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));130131const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, {});132133assert.notEqual(actual, null);134assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });135assert.deepStrictEqual(actual!.location.toString(), extensionLocation.toString());136assert.deepStrictEqual(actual!.isBuiltin, false);137assert.deepStrictEqual(actual!.type, ExtensionType.User);138assert.deepStrictEqual(actual!.isValid, true);139assert.deepStrictEqual(actual!.validations, []);140assert.deepStrictEqual(actual!.metadata, undefined);141assert.deepStrictEqual(actual!.targetPlatform, TargetPlatform.UNDEFINED);142assert.deepStrictEqual(actual!.manifest, manifest);143});144145test('scan single extension', async () => {146const manifest: Partial<IExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' });147const extensionLocation = await aUserExtension(manifest);148const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));149150const actual = await testObject.scanOneOrMultipleExtensions(extensionLocation, ExtensionType.User, {});151152assert.deepStrictEqual(actual.length, 1);153assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });154assert.deepStrictEqual(actual[0].location.toString(), extensionLocation.toString());155assert.deepStrictEqual(actual[0].isBuiltin, false);156assert.deepStrictEqual(actual[0].type, ExtensionType.User);157assert.deepStrictEqual(actual[0].isValid, true);158assert.deepStrictEqual(actual[0].validations, []);159assert.deepStrictEqual(actual[0].metadata, undefined);160assert.deepStrictEqual(actual[0].targetPlatform, TargetPlatform.UNDEFINED);161assert.deepStrictEqual(actual[0].manifest, manifest);162});163164test('scan multiple extensions', async () => {165const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));166await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' }));167const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));168169const actual = await testObject.scanOneOrMultipleExtensions(dirname(extensionLocation), ExtensionType.User, {});170171assert.deepStrictEqual(actual.length, 2);172assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });173assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' });174});175176test('scan all user extensions with different versions', async () => {177await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));178await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' }));179const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));180181const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });182183assert.deepStrictEqual(actual.length, 1);184assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });185assert.deepStrictEqual(actual[0].manifest.version, '1.0.2');186});187188test('scan all user extensions include all versions', async () => {189await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));190await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' }));191const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));192193const actual = await testObject.scanAllUserExtensions();194195assert.deepStrictEqual(actual.length, 2);196assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });197assert.deepStrictEqual(actual[0].manifest.version, '1.0.1');198assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name' });199assert.deepStrictEqual(actual[1].manifest.version, '1.0.2');200});201202test('scan all user extensions with different versions and higher version is not compatible', async () => {203await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));204await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2', engines: { vscode: '^1.67.0' } }));205const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));206207const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });208209assert.deepStrictEqual(actual.length, 1);210assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });211assert.deepStrictEqual(actual[0].manifest.version, '1.0.1');212});213214test('scan all user extensions exclude invalid extensions', async () => {215await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));216await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } }));217const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));218219const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: false });220221assert.deepStrictEqual(actual.length, 1);222assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });223});224225test('scan all user extensions include invalid extensions', async () => {226await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }));227await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } }));228const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));229230const actual = await testObject.scanAllUserExtensions({ includeAllVersions: false, includeInvalid: true });231232assert.deepStrictEqual(actual.length, 2);233assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });234assert.deepStrictEqual(actual[1].identifier, { id: 'pub.name2' });235});236237test('scan system extensions include additional builtin extensions', async () => {238instantiationService.stub(IProductService, {239version: '1.66.0',240builtInExtensions: [241{ name: 'pub.name2', version: '', repo: '', metadata: undefined },242{ name: 'pub.name', version: '', repo: '', metadata: undefined }243]244});245await anExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' }), joinPath(ROOT, 'additional'));246const extensionLocation = await anExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }), joinPath(ROOT, 'additional'));247await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' }));248await instantiationService.get(IFileService).writeFile(joinPath(instantiationService.get(INativeEnvironmentService).userHome, '.vscode-oss-dev', 'extensions', 'control.json'), VSBuffer.fromString(JSON.stringify({ 'pub.name2': 'disabled', 'pub.name': extensionLocation.fsPath })));249const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));250251const actual = await testObject.scanSystemExtensions({ checkControlFile: true });252253assert.deepStrictEqual(actual.length, 1);254assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });255assert.deepStrictEqual(actual[0].manifest.version, '1.0.0');256});257258test('scan all user extensions with default nls replacements', async () => {259const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', displayName: '%displayName%' }));260await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' })));261const testObject = disposables.add(instantiationService.createInstance(ExtensionsScannerService));262263const actual = await testObject.scanAllUserExtensions();264265assert.deepStrictEqual(actual.length, 1);266assert.deepStrictEqual(actual[0].identifier, { id: 'pub.name' });267assert.deepStrictEqual(actual[0].manifest.displayName, 'Hello World');268});269270test('scan extension with en nls replacements', async () => {271const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', displayName: '%displayName%' }));272await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' })));273const nlsLocation = joinPath(extensionLocation, 'package.en.json');274await instantiationService.get(IFileService).writeFile(nlsLocation, VSBuffer.fromString(JSON.stringify({ contents: { package: { displayName: 'Hello World EN' } } })));275const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));276277translations = { 'pub.name': nlsLocation.fsPath };278const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, { language: 'en' });279280assert.ok(actual !== null);281assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });282assert.deepStrictEqual(actual!.manifest.displayName, 'Hello World EN');283});284285test('scan extension falls back to default nls replacements', async () => {286const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', displayName: '%displayName%' }));287await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' })));288const nlsLocation = joinPath(extensionLocation, 'package.en.json');289await instantiationService.get(IFileService).writeFile(nlsLocation, VSBuffer.fromString(JSON.stringify({ contents: { package: { displayName: 'Hello World EN' } } })));290const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));291292translations = { 'pub.name2': nlsLocation.fsPath };293const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, { language: 'en' });294295assert.ok(actual !== null);296assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });297assert.deepStrictEqual(actual!.manifest.displayName, 'Hello World');298});299300test('scan single extension with manifest metadata retains manifest metadata', async () => {301const manifest: Partial<IExtensionManifest> = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' });302const expectedMetadata = { size: 12345, installedTimestamp: 1234567890, targetPlatform: TargetPlatform.DARWIN_ARM64 };303const extensionLocation = await aUserExtension({304...manifest,305__metadata: expectedMetadata306});307const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService));308309const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, {});310311assert.notStrictEqual(actual, null);312assert.deepStrictEqual(actual!.identifier, { id: 'pub.name' });313assert.deepStrictEqual(actual!.location.toString(), extensionLocation.toString());314assert.deepStrictEqual(actual!.isBuiltin, false);315assert.deepStrictEqual(actual!.type, ExtensionType.User);316assert.deepStrictEqual(actual!.isValid, true);317assert.deepStrictEqual(actual!.validations, []);318assert.deepStrictEqual(actual!.metadata, expectedMetadata);319assert.deepStrictEqual(actual!.manifest, manifest);320});321322async function aUserExtension(manifest: Partial<IScannedExtensionManifest>): Promise<URI> {323const environmentService = instantiationService.get(INativeEnvironmentService);324return anExtension(manifest, URI.file(environmentService.extensionsPath));325}326327async function aSystemExtension(manifest: Partial<IScannedExtensionManifest>): Promise<URI> {328const environmentService = instantiationService.get(INativeEnvironmentService);329return anExtension(manifest, URI.file(environmentService.builtinExtensionsPath));330}331332async function anExtension(manifest: Partial<IScannedExtensionManifest>, root: URI): Promise<URI> {333const fileService = instantiationService.get(IFileService);334const extensionLocation = joinPath(root, `${manifest.publisher}.${manifest.name}-${manifest.version}`);335await fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest)));336return extensionLocation;337}338339function anExtensionManifest(manifest: Partial<IScannedExtensionManifest>): Partial<IExtensionManifest> {340return { engines: { vscode: '^1.66.0' }, version: '1.0.0', main: 'main.js', activationEvents: ['*'], ...manifest };341}342});343344suite('ExtensionScannerInput', () => {345346ensureNoDisposablesAreLeakedInTestSuite();347348test('compare inputs - location', () => {349const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(location, mtime, undefined, undefined, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {});350351assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true);352assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true);353assert.strictEqual(ExtensionScannerInput.equals(anInput(joinPath(ROOT, 'foo'), undefined), anInput(ROOT, undefined)), false);354assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 200)), false);355assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, 200)), false);356});357358test('compare inputs - application location', () => {359const anInput = (location: URI, mtime: number | undefined) => new ExtensionScannerInput(ROOT, undefined, location, mtime, false, undefined, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {});360361assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, undefined)), true);362assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 100)), true);363assert.strictEqual(ExtensionScannerInput.equals(anInput(joinPath(ROOT, 'foo'), undefined), anInput(ROOT, undefined)), false);364assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, 100), anInput(ROOT, 200)), false);365assert.strictEqual(ExtensionScannerInput.equals(anInput(ROOT, undefined), anInput(ROOT, 200)), false);366});367368test('compare inputs - profile', () => {369const anInput = (profile: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, profile, profileScanOptions, ExtensionType.User, true, '1.1.1', undefined, undefined, true, undefined, {});370371assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: true })), true);372assert.strictEqual(ExtensionScannerInput.equals(anInput(false, { bailOutWhenFileNotFound: true }), anInput(false, { bailOutWhenFileNotFound: true })), true);373assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: false }), anInput(true, { bailOutWhenFileNotFound: false })), true);374assert.strictEqual(ExtensionScannerInput.equals(anInput(true, {}), anInput(true, {})), true);375assert.strictEqual(ExtensionScannerInput.equals(anInput(true, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: false })), false);376assert.strictEqual(ExtensionScannerInput.equals(anInput(true, {}), anInput(true, { bailOutWhenFileNotFound: true })), false);377assert.strictEqual(ExtensionScannerInput.equals(anInput(true, undefined), anInput(true, {})), false);378assert.strictEqual(ExtensionScannerInput.equals(anInput(false, { bailOutWhenFileNotFound: true }), anInput(true, { bailOutWhenFileNotFound: true })), false);379});380381test('compare inputs - extension type', () => {382const anInput = (type: ExtensionType) => new ExtensionScannerInput(ROOT, undefined, undefined, undefined, false, undefined, type, true, '1.1.1', undefined, undefined, true, undefined, {});383384assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.System), anInput(ExtensionType.System)), true);385assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.User), anInput(ExtensionType.User)), true);386assert.strictEqual(ExtensionScannerInput.equals(anInput(ExtensionType.User), anInput(ExtensionType.System)), false);387});388389});390391392