diff --git a/src/client/banner.ts b/src/client/banner.ts index fb839d5e848e..67fc890ff951 100644 --- a/src/client/banner.ts +++ b/src/client/banner.ts @@ -17,7 +17,8 @@ export class BannerService { if (!this.shouldShowBanner.value) { return; } - this.shouldShowBanner.value = false; + this.shouldShowBanner.updateValue(false) + .catch(ex => console.error('Python Extension: Failed to update banner value', ex)); const message = 'The Python extension is now published by Microsoft!'; const yesButton = 'Read more'; diff --git a/src/client/common/featureDeprecationManager.ts b/src/client/common/featureDeprecationManager.ts index dea79684e306..5ecd868a23be 100644 --- a/src/client/common/featureDeprecationManager.ts +++ b/src/client/common/featureDeprecationManager.ts @@ -112,7 +112,7 @@ export class FeatureDeprecationManager implements IFeatureDeprecationManager { break; } case doNotShowAgain: { - notificationPromptEnabled.value = false; + await notificationPromptEnabled.updateValue(false); break; } default: { diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index 37e19af5f0fa..a882db7cbc1f 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -14,8 +14,8 @@ class PersistentState implements IPersistentState{ return this.storage.get(this.key, this.defaultValue); } - public set value(newValue: T) { - this.storage.update(this.key, newValue); + public async updateValue(newValue: T): Promise { + await this.storage.update(this.key, newValue); } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 54fc44bf32d2..ab7eadf3dbf8 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -14,7 +14,8 @@ export const GLOBAL_MEMENTO = Symbol('IGlobalMemento'); export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento'); export interface IPersistentState { - value: T; + readonly value: T; + updateValue(value: T): Promise; } export const IPersistentStateFactory = Symbol('IPersistentStateFactory'); diff --git a/src/client/interpreter/configuration/interpreterSelector.ts b/src/client/interpreter/configuration/interpreterSelector.ts index 220f916af4a2..0d3acb8a89e7 100644 --- a/src/client/interpreter/configuration/interpreterSelector.ts +++ b/src/client/interpreter/configuration/interpreterSelector.ts @@ -43,11 +43,12 @@ export class InterpreterSelector implements Disposable { if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; } + const cachedPrefix = suggestion.cachedEntry ? '(cached) ' : ''; return { // tslint:disable-next-line:no-non-null-assertion label: suggestion.displayName!, description: suggestion.companyDisplayName || '', - detail: detail, + detail: `${cachedPrefix}${detail}`, path: suggestion.path }; } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index d33e07befcac..70c52be3260b 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -62,6 +62,7 @@ export type PythonInterpreter = { type: InterpreterType; envName?: string; envPath?: string; + cachedEntry?: boolean; }; export type WorkspacePythonPath = { diff --git a/src/client/interpreter/index.ts b/src/client/interpreter/index.ts index 1f24e417b25e..f7c3c67c4e7d 100644 --- a/src/client/interpreter/index.ts +++ b/src/client/interpreter/index.ts @@ -53,7 +53,7 @@ export class InterpreterManager implements Disposable, IInterpreterService { } const virtualEnvMgr = this.serviceContainer.get(IVirtualEnvironmentManager); const versionService = this.serviceContainer.get(IInterpreterVersionService); - const virtualEnvInterpreterProvider = new VirtualEnvService([activeWorkspace.folderUri.fsPath], virtualEnvMgr, versionService); + const virtualEnvInterpreterProvider = new VirtualEnvService([activeWorkspace.folderUri.fsPath], virtualEnvMgr, versionService, this.serviceContainer); const interpreters = await virtualEnvInterpreterProvider.getInterpreters(activeWorkspace.folderUri); const workspacePathUpper = activeWorkspace.folderUri.fsPath.toUpperCase(); diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index 2be228e18c5c..e287cddbf8a9 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -1,7 +1,7 @@ import { inject, injectable } from 'inversify'; import * as _ from 'lodash'; import * as path from 'path'; -import { Disposable, Uri, workspace } from 'vscode'; +import { Disposable, Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IDisposableRegistry } from '../../common/types'; import { arePathsSame } from '../../common/utils'; @@ -21,35 +21,21 @@ import { fixInterpreterDisplayName } from './helpers'; @injectable() export class PythonInterpreterLocatorService implements IInterpreterLocatorService { - private interpretersPerResource: Map>; private disposables: Disposable[] = []; private platform: IPlatformService; constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.interpretersPerResource = new Map>(); serviceContainer.get(IDisposableRegistry).push(this); this.platform = serviceContainer.get(IPlatformService); } public async getInterpreters(resource?: Uri) { - const resourceKey = this.getResourceKey(resource); - if (!this.interpretersPerResource.has(resourceKey)) { - this.interpretersPerResource.set(resourceKey, this.getInterpretersPerResource(resource)); - } - - return await this.interpretersPerResource.get(resourceKey)!; + return this.getInterpretersPerResource(resource); } public dispose() { this.disposables.forEach(disposable => disposable.dispose()); } - private getResourceKey(resource?: Uri) { - if (!resource) { - return ''; - } - const workspaceFolder = workspace.getWorkspaceFolder(resource); - return workspaceFolder ? workspaceFolder.uri.fsPath : ''; - } private async getInterpretersPerResource(resource?: Uri) { - const locators = this.getLocators(resource); + const locators = this.getLocators(); const promises = locators.map(async provider => provider.getInterpreters(resource)); const listOfInterpreters = await Promise.all(promises); @@ -73,7 +59,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi return accumulator; }, []); } - private getLocators(resource?: Uri) { + private getLocators() { const locators: IInterpreterLocatorService[] = []; // The order of the services is important. if (this.platform.isWindows) { diff --git a/src/client/interpreter/locators/services/KnownPathsService.ts b/src/client/interpreter/locators/services/KnownPathsService.ts index 10b3d44f9679..1fb6b99f57fa 100644 --- a/src/client/interpreter/locators/services/KnownPathsService.ts +++ b/src/client/interpreter/locators/services/KnownPathsService.ts @@ -3,22 +3,26 @@ import * as _ from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils'; -import { IInterpreterLocatorService, IInterpreterVersionService, IKnownSearchPathsForInterpreters, InterpreterType } from '../../contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { IInterpreterVersionService, IKnownSearchPathsForInterpreters, InterpreterType, PythonInterpreter } from '../../contracts'; import { lookForInterpretersInDirectory } from '../helpers'; +import { CacheableLocatorService } from './cacheableLocatorService'; // tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); @injectable() -export class KnownPathsService implements IInterpreterLocatorService { +export class KnownPathsService extends CacheableLocatorService { public constructor( @inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: string[], - @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService) { } - // tslint:disable-next-line:no-shadowed-variable - public getInterpreters(_?: Uri) { - return this.suggestionsFromKnownPaths(); + @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, + @inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('KnownPathsService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.suggestionsFromKnownPaths(); + } private suggestionsFromKnownPaths() { const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir)); return Promise.all(promises) diff --git a/src/client/interpreter/locators/services/cacheableLocatorService.ts b/src/client/interpreter/locators/services/cacheableLocatorService.ts new file mode 100644 index 000000000000..ffa7ed01c8de --- /dev/null +++ b/src/client/interpreter/locators/services/cacheableLocatorService.ts @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { createDeferred, Deferred } from '../../../common/helpers'; +import { IPersistentStateFactory } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; + +@injectable() +export abstract class CacheableLocatorService implements IInterpreterLocatorService { + private getInterpretersPromise: Deferred; + private readonly cacheKey: string; + constructor(name: string, + protected readonly serviceContainer: IServiceContainer) { + this.cacheKey = `INTERPRETERS_CACHE_${name}`; + } + public abstract dispose(); + public async getInterpreters(resource?: Uri): Promise { + if (!this.getInterpretersPromise) { + this.getInterpretersPromise = createDeferred(); + this.getInterpretersImplementation(resource) + .then(async items => { + await this.cacheInterpreters(items); + this.getInterpretersPromise.resolve(items); + }) + .catch(ex => this.getInterpretersPromise.reject(ex)); + } + if (this.getInterpretersPromise.completed) { + return this.getInterpretersPromise.promise; + } + + const cachedInterpreters = this.getCachedInterpreters(); + return Array.isArray(cachedInterpreters) ? cachedInterpreters : this.getInterpretersPromise.promise; + } + + protected abstract getInterpretersImplementation(resource?: Uri): Promise; + + private getCachedInterpreters() { + const persistentFactory = this.serviceContainer.get(IPersistentStateFactory); + // tslint:disable-next-line:no-any + const globalPersistence = persistentFactory.createGlobalPersistentState(this.cacheKey, undefined as any); + if (!Array.isArray(globalPersistence.value)) { + return; + } + return globalPersistence.value.map(item => { + return { + ...item, + cachedEntry: true + }; + }); + } + private async cacheInterpreters(interpreters: PythonInterpreter[]) { + const persistentFactory = this.serviceContainer.get(IPersistentStateFactory); + const globalPersistence = persistentFactory.createGlobalPersistentState(this.cacheKey, []); + await globalPersistence.updateValue(interpreters); + } +} diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index 24034e16e74b..49cbc2eec935 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -3,27 +3,30 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; import { ILogger } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; import { ICondaService, - IInterpreterLocatorService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { CacheableLocatorService } from './cacheableLocatorService'; import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName } from './conda'; @injectable() -export class CondaEnvFileService implements IInterpreterLocatorService { +export class CondaEnvFileService extends CacheableLocatorService { constructor( @inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, @inject(ICondaService) private condaService: ICondaService, @inject(IFileSystem) private fileSystem: IFileSystem, + @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(ILogger) private logger: ILogger) { - } - public async getInterpreters(_?: Uri) { - return this.getSuggestionsFromConda(); + super('CondaEnvFileService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.getSuggestionsFromConda(); + } private async getSuggestionsFromConda(): Promise { if (!this.condaService.condaEnvironmentsFile) { return []; diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index bc2a0ef63ac6..7e2d5c616fcc 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -5,20 +5,21 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; import { ILogger } from '../../../common/types'; -import { CondaInfo, ICondaService, IInterpreterLocatorService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { CondaInfo, ICondaService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { CacheableLocatorService } from './cacheableLocatorService'; import { AnacondaCompanyName, AnacondaCompanyNames } from './conda'; import { CondaHelper } from './condaHelper'; @injectable() -export class CondaEnvService implements IInterpreterLocatorService { +export class CondaEnvService extends CacheableLocatorService { private readonly condaHelper = new CondaHelper(); constructor( @inject(ICondaService) private condaService: ICondaService, @inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, @inject(ILogger) private logger: ILogger, + @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(IFileSystem) private fileSystem: IFileSystem) { - } - public async getInterpreters(resource?: Uri) { - return this.getSuggestionsFromConda(); + super('CondaEnvService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } @@ -62,6 +63,9 @@ export class CondaEnvService implements IInterpreterLocatorService { // tslint:disable-next-line:no-non-null-assertion .then(interpreters => interpreters.map(interpreter => interpreter!)); } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.getSuggestionsFromConda(); + } private stripCompanyName(content: string) { // Strip company name from version. const startOfCompanyName = AnacondaCompanyNames.reduce((index, companyName) => { diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 55578d7a1c30..3620eb6c87d5 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -132,10 +132,10 @@ export class CondaService implements ICondaService { const condaFile = await this.getCondaFile(); const envInfo = await this.processService.exec(condaFile, ['env', 'list']).then(output => output.stdout); const environments = this.condaHelper.parseCondaEnvironmentNames(envInfo); - globalPersistence.value = { data: environments }; + await globalPersistence.updateValue({ data: environments }); return environments; } catch (ex) { - globalPersistence.value = { data: undefined }; + await globalPersistence.updateValue({ data: undefined }); // Failed because either: // 1. conda is not installed. // 2. `conda env list has changed signature. diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index fb1940e8aee9..62d934ff5634 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -4,19 +4,24 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../common/configSettings'; import { IProcessService } from '../../../common/process/types'; -import { IInterpreterLocatorService, IInterpreterVersionService, InterpreterType } from '../../contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; import { IVirtualEnvironmentManager } from '../../virtualEnvs/types'; +import { CacheableLocatorService } from './cacheableLocatorService'; @injectable() -export class CurrentPathService implements IInterpreterLocatorService { +export class CurrentPathService extends CacheableLocatorService { public constructor( @inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager, @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, - @inject(IProcessService) private processService: IProcessService) { } - public async getInterpreters(resource?: Uri) { - return this.suggestionsFromKnownPaths(); + @inject(IProcessService) private processService: IProcessService, + @inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('CurrentPathService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.suggestionsFromKnownPaths(); + } private async suggestionsFromKnownPaths(resource?: Uri) { const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]); const python = this.getInterpreter('python', '').then(interpreter => [interpreter]); diff --git a/src/client/interpreter/locators/services/virtualEnvService.ts b/src/client/interpreter/locators/services/virtualEnvService.ts index 8b859fb2facb..d073c42d51ec 100644 --- a/src/client/interpreter/locators/services/virtualEnvService.ts +++ b/src/client/interpreter/locators/services/virtualEnvService.ts @@ -3,24 +3,29 @@ import * as _ from 'lodash'; import * as path from 'path'; import { Uri, workspace } from 'vscode'; import { fsReaddirAsync, IS_WINDOWS } from '../../../common/utils'; -import { IInterpreterLocatorService, IInterpreterVersionService, IKnownSearchPathsForVirtualEnvironments, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { IInterpreterVersionService, IKnownSearchPathsForVirtualEnvironments, InterpreterType, PythonInterpreter } from '../../contracts'; import { IVirtualEnvironmentManager } from '../../virtualEnvs/types'; import { lookForInterpretersInDirectory } from '../helpers'; import * as settings from './../../../common/configSettings'; +import { CacheableLocatorService } from './cacheableLocatorService'; // tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); @injectable() -export class VirtualEnvService implements IInterpreterLocatorService { +export class VirtualEnvService extends CacheableLocatorService { public constructor( @inject(IKnownSearchPathsForVirtualEnvironments) private knownSearchPaths: string[], @inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager, - @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService) { } - public async getInterpreters(resource?: Uri) { - return this.suggestionsFromKnownVenvs(); + @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, + @inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('KnownPathsService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.suggestionsFromKnownVenvs(); + } private async suggestionsFromKnownVenvs() { return Promise.all(this.knownSearchPaths.map(dir => this.lookForInterpretersInVenvs(dir))) // tslint:disable-next-line:underscore-consistent-invocation diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index d238cd4b31e3..dc724fc7e7c8 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -5,7 +5,9 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture, IRegistry, RegistryHive } from '../../../common/platform/types'; import { Is64Bit } from '../../../common/types'; -import { IInterpreterLocatorService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { InterpreterType, PythonInterpreter } from '../../contracts'; +import { CacheableLocatorService } from './cacheableLocatorService'; import { AnacondaCompanyName, AnacondaCompanyNames } from './conda'; // tslint:disable-next-line:variable-name @@ -24,16 +26,17 @@ type CompanyInterpreter = { }; @injectable() -export class WindowsRegistryService implements IInterpreterLocatorService { - constructor( @inject(IRegistry) private registry: IRegistry, @inject(Is64Bit) private is64Bit: boolean) { - - } - // tslint:disable-next-line:variable-name - public getInterpreters(_resource?: Uri) { - return this.getInterpretersFromRegistry(); +export class WindowsRegistryService extends CacheableLocatorService { + constructor( @inject(IRegistry) private registry: IRegistry, + @inject(Is64Bit) private is64Bit: boolean, + @inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('WindowsRegistryService', serviceContainer); } // tslint:disable-next-line:no-empty public dispose() { } + protected getInterpretersImplementation(resource?: Uri): Promise { + return this.getInterpretersFromRegistry(); + } private async getInterpretersFromRegistry() { // https://github.com/python/peps/blob/master/pep-0514.txt#L357 const hkcuArch = this.is64Bit ? undefined : Architecture.x86; diff --git a/src/test/interpreters/condaEnvFileService.test.ts b/src/test/interpreters/condaEnvFileService.test.ts index 44624de4906a..85844e19c022 100644 --- a/src/test/interpreters/condaEnvFileService.test.ts +++ b/src/test/interpreters/condaEnvFileService.test.ts @@ -3,11 +3,13 @@ import { EOL } from 'os'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; -import { ILogger } from '../../client/common/types'; +import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { ICondaService, IInterpreterLocatorService, IInterpreterVersionService, InterpreterType } from '../../client/interpreter/contracts'; import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvFileService } from '../../client/interpreter/locators/services/condaEnvFileService'; +import { IServiceContainer } from '../../client/ioc/types'; import { initialize, initializeTest } from '../initialize'; +import { MockState } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); const environmentsFilePath = path.join(environmentsPath, 'environments.txt'); @@ -22,11 +24,17 @@ suite('Interpreters from Conda Environments Text File', () => { suiteSetup(initialize); setup(async () => { await initializeTest(); + const serviceContainer = TypeMoq.Mock.ofType(); + const stateFactory = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + const state = new MockState(undefined); + stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); + condaService = TypeMoq.Mock.ofType(); interpreterVersion = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); logger = TypeMoq.Mock.ofType(); - condaFileProvider = new CondaEnvFileService(interpreterVersion.object, condaService.object, fileSystem.object, logger.object); + condaFileProvider = new CondaEnvFileService(interpreterVersion.object, condaService.object, fileSystem.object, serviceContainer.object, logger.object); }); test('Must return an empty list if environment file cannot be found', async () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => undefined); diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index 7bdcba40abde..a3c7b93c7acc 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -2,12 +2,14 @@ import * as assert from 'assert'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; -import { ILogger } from '../../client/common/types'; +import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { ICondaService, IInterpreterVersionService, InterpreterType } from '../../client/interpreter/contracts'; import { AnacondaCompanyName, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService'; +import { IServiceContainer } from '../../client/ioc/types'; import { initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; +import { MockState } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); @@ -23,10 +25,16 @@ suite('Interpreters from Conda Environments', () => { setup(async () => { await initializeTest(); initializeDI(); + const serviceContainer = TypeMoq.Mock.ofType(); + const stateFactory = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + const state = new MockState(undefined); + stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); + condaService = TypeMoq.Mock.ofType(); interpreterVersion = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - condaProvider = new CondaEnvService(condaService.object, interpreterVersion.object, logger.object, fileSystem.object); + condaProvider = new CondaEnvService(condaService.object, interpreterVersion.object, logger.object, serviceContainer.object, fileSystem.object); }); teardown(() => ioc.dispose()); function initializeDI() { diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index 7a44bad4ba38..d88ce3c9d181 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -6,28 +6,17 @@ import * as TypeMoq from 'typemoq'; import { FileSystem } from '../../client/common/platform/fileSystem'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IProcessService } from '../../client/common/process/types'; -import { ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { IInterpreterLocatorService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { CondaService, KNOWN_CONDA_LOCATIONS } from '../../client/interpreter/locators/services/condaService'; import { IServiceContainer } from '../../client/ioc/types'; +import { MockState } from './mocks'; // tslint:disable-next-line:no-require-imports no-var-requires const untildify: (value: string) => string = require('untildify'); const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -// tslint:disable-next-line:no-any -class MockState implements IPersistentState { - // tslint:disable-next-line:no-any - constructor(public data: any) { } - // tslint:disable-next-line:no-any - get value(): any { - return this.data; - } - set value(data) { - this.data = data; - } -} // tslint:disable-next-line:max-func-body-length suite('Interpreters Conda Service', () => { let logger: TypeMoq.IMock; diff --git a/src/test/interpreters/mocks.ts b/src/test/interpreters/mocks.ts index 2bfc9b1cf9a0..3919ebfb407f 100644 --- a/src/test/interpreters/mocks.ts +++ b/src/test/interpreters/mocks.ts @@ -1,5 +1,6 @@ import { injectable } from 'inversify'; import { Architecture, IRegistry, RegistryHive } from '../../client/common/platform/types'; +import { IPersistentState } from '../../client/common/types'; import { IInterpreterVersionService, InterpreterType } from '../../client/interpreter/contracts'; import { IVirtualEnvironmentIdentifier } from '../../client/interpreter/virtualEnvs/types'; @@ -60,3 +61,16 @@ export class MockInterpreterVersionProvider implements IInterpreterVersionServic // tslint:disable-next-line:no-empty public dispose() { } } + +// tslint:disable-next-line:no-any max-classes-per-file +export class MockState implements IPersistentState { + // tslint:disable-next-line:no-any + constructor(public data: any) { } + // tslint:disable-next-line:no-any + get value(): any { + return this.data; + } + public async updateValue(data): Promise { + this.data = data; + } +} diff --git a/src/test/interpreters/windowsRegistryService.test.ts b/src/test/interpreters/windowsRegistryService.test.ts index 03e37928b458..eba551ea4b50 100644 --- a/src/test/interpreters/windowsRegistryService.test.ts +++ b/src/test/interpreters/windowsRegistryService.test.ts @@ -1,28 +1,39 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as TypeMoq from 'typemoq'; import { Architecture, RegistryHive } from '../../client/common/platform/types'; +import { IPersistentStateFactory } from '../../client/common/types'; import { IS_WINDOWS } from '../../client/debugger/Common/Utils'; import { WindowsRegistryService } from '../../client/interpreter/locators/services/windowsRegistryService'; +import { IServiceContainer } from '../../client/ioc/types'; import { initialize, initializeTest } from '../initialize'; -import { MockRegistry } from './mocks'; +import { MockRegistry, MockState } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); // tslint:disable-next-line:max-func-body-length suite('Interpreters from Windows Registry', () => { + let serviceContainer: TypeMoq.IMock; suiteSetup(initialize); - setup(initializeTest); + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + const stateFactory = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + const state = new MockState(undefined); + stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); + return initializeTest(); + }); if (IS_WINDOWS) { test('Must return an empty list (x86)', async () => { const registry = new MockRegistry([], []); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); test('Must return an empty list (x64)', async () => { const registry = new MockRegistry([], []); - const winRegistry = new WindowsRegistryService(registry, true); + const winRegistry = new WindowsRegistryService(registry, true, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); @@ -40,7 +51,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -60,7 +71,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\PythonCore\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -80,7 +91,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\PyLauncher\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'c:/temp/Install Path Tag1' } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -95,7 +106,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -137,7 +148,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe') } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -196,7 +207,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters(); @@ -255,7 +266,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') } ]; const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); const interpreters = await winRegistry.getInterpreters();