diff --git a/src/client/application/diagnostics/checks/macPythonInterpreter.ts b/src/client/application/diagnostics/checks/macPythonInterpreter.ts index f6ed45b551ae..7c2acce2bf67 100644 --- a/src/client/application/diagnostics/checks/macPythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/macPythonInterpreter.ts @@ -95,7 +95,7 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { return []; } - if (!this.helper.isMacDefaultPythonPath(settings.pythonPath)) { + if (!(await this.helper.isMacDefaultPythonPath(settings.pythonPath))) { return []; } if (!currentInterpreter || currentInterpreter.envType !== EnvironmentType.Unknown) { @@ -103,18 +103,19 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { } const interpreters = await this.interpreterService.getInterpreters(resource); - if (interpreters.filter((i) => !this.helper.isMacDefaultPythonPath(i.path)).length === 0) { - return [ - new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic, - resource - ) - ]; + for (const info of interpreters) { + if (!(await this.helper.isMacDefaultPythonPath(info.path))) { + return [ + new InvalidMacPythonInterpreterDiagnostic( + DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, + resource + ) + ]; + } } - return [ new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, + DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic, resource ) ]; diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index f6432785bf71..1092b00236b0 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -71,7 +71,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { processService, this.fileSystem, undefined, - this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath) + await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath) ); } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index c1ad838a9e10..179415189f64 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -100,7 +100,7 @@ export const IInterpreterHelper = Symbol('IInterpreterHelper'); export interface IInterpreterHelper { getActiveWorkspaceUri(resource: Resource): WorkspacePythonPath | undefined; getInterpreterInformation(pythonPath: string): Promise>; - isMacDefaultPythonPath(pythonPath: string): Boolean; + isMacDefaultPythonPath(pythonPath: string): Promise; getInterpreterTypeDisplayName(interpreterType: EnvironmentType): string | undefined; getBestInterpreter(interpreters?: PythonEnvironment[]): PythonEnvironment | undefined; } diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index 59e7f213552d..daf7f6af2a37 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -15,7 +15,7 @@ import { PythonEnvironment, sortInterpreters } from '../pythonEnvironments/info'; -import { IInterpreterHelper } from './contracts'; +import { IComponentAdapter, IInterpreterHelper } from './contracts'; import { IInterpreterHashProviderFactory } from './locators/types'; const EXPITY_DURATION = 24 * 60 * 60 * 1000; @@ -44,12 +44,19 @@ export function isInterpreterLocatedInWorkspace(interpreter: PythonEnvironment, return interpreterPath.startsWith(resourcePath); } +// The parts of IComponentAdapter used here. +interface IComponent { + getInterpreterInformation(pythonPath: string): Promise>; + isMacDefaultPythonPath(pythonPath: string): Promise; +} + @injectable() export class InterpreterHelper implements IInterpreterHelper { private readonly persistentFactory: IPersistentStateFactory; constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory + @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory, + @inject(IComponentAdapter) private readonly pyenvs: IComponent ) { this.persistentFactory = this.serviceContainer.get(IPersistentStateFactory); } @@ -78,6 +85,11 @@ export class InterpreterHelper implements IInterpreterHelper { } } public async getInterpreterInformation(pythonPath: string): Promise> { + const found = await this.pyenvs.getInterpreterInformation(pythonPath); + if (found !== undefined) { + return found; + } + const fileHash = await this.hashProviderFactory .create({ pythonPath }) .then((provider) => provider.getInterpreterHash(pythonPath)) @@ -115,7 +127,11 @@ export class InterpreterHelper implements IInterpreterHelper { return; } } - public isMacDefaultPythonPath(pythonPath: string) { + public async isMacDefaultPythonPath(pythonPath: string): Promise { + const result = await this.pyenvs.isMacDefaultPythonPath(pythonPath); + if (result !== undefined) { + return result; + } return isMacDefaultPythonPath(pythonPath); } public getInterpreterTypeDisplayName(interpreterType: EnvironmentType) { diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 5fa9202b95cc..6d75fa78d65d 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -25,6 +25,7 @@ import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; import { captureTelemetry } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { + IComponentAdapter, IInterpreterDisplay, IInterpreterHelper, IInterpreterLocatorService, @@ -40,6 +41,11 @@ export type GetInterpreterOptions = { onSuggestion?: boolean; }; +// The parts of IComponentAdapter used here. +interface IComponent { + getInterpreterDetails(pythonPath: string): Promise; +} + @injectable() export class InterpreterService implements Disposable, IInterpreterService { public get hasInterpreters(): Promise { @@ -70,7 +76,8 @@ export class InterpreterService implements Disposable, IInterpreterService { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory + @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory, + @inject(IComponentAdapter) private readonly pyenvs: IComponent ) { this.locator = serviceContainer.get( IInterpreterLocatorService, @@ -160,6 +167,11 @@ export class InterpreterService implements Disposable, IInterpreterService { return this.getInterpreterDetails(fullyQualifiedPath, resource); } public async getInterpreterDetails(pythonPath: string, resource?: Uri): Promise { + const info = await this.pyenvs.getInterpreterDetails(pythonPath); + if (info !== undefined) { + return info; + } + // If we don't have the fully qualified path, then get it. if (path.basename(pythonPath) === pythonPath) { const pythonExecutionFactory = this.serviceContainer.get(IPythonExecutionFactory); diff --git a/src/client/interpreter/locators/types.ts b/src/client/interpreter/locators/types.ts index 8cb5a9d15c08..0b4ec5afab4b 100644 --- a/src/client/interpreter/locators/types.ts +++ b/src/client/interpreter/locators/types.ts @@ -53,7 +53,7 @@ export interface IWindowsStoreInterpreter { * @returns {boolean} * @memberof WindowsStoreInterpreter */ - isWindowsStoreInterpreter(pythonPath: string): boolean; + isWindowsStoreInterpreter(pythonPath: string): Promise; /** * Whether this is a python executable in a windows app store folder that is internal and can be hidden from users. * diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts index 505cd2042046..67f38e0fe769 100644 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ b/src/client/pythonEnvironments/discovery/locators/index.ts @@ -15,6 +15,7 @@ import { CONDA_ENV_SERVICE, CURRENT_PATH_SERVICE, GLOBAL_VIRTUAL_ENV_SERVICE, + IComponentAdapter, IInterpreterLocatorHelper, IInterpreterLocatorService, KNOWN_PATH_SERVICE, @@ -180,6 +181,12 @@ function matchURI(uri: Uri, ...candidates: Uri[]): boolean { return matchedUri !== undefined; } +// The parts of IComponentAdapter used here. +interface IComponent { + hasInterpreters: Promise; + getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise; +} + /** * Facilitates locating Python interpreters. */ @@ -198,7 +205,10 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi private readonly onLocatingEmitter:EventEmitter> = new EventEmitter>(); - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + constructor( + @inject(IServiceContainer) private serviceContainer: IServiceContainer, + @inject(IComponentAdapter) private readonly pyenvs: IComponent, + ) { this._hasInterpreters = createDeferred(); serviceContainer.get(IDisposableRegistry).push(this); this.platform = serviceContainer.get(IPlatformService); @@ -219,7 +229,12 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi } public get hasInterpreters(): Promise { - return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); + return this.pyenvs.hasInterpreters.then((res) => { + if (res !== undefined) { + return res; + } + return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); + }); } /** @@ -239,6 +254,10 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi */ @traceDecorators.verbose('Get Interpreters') public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise { + const envs = await this.pyenvs.getInterpreters(resource, options); + if (envs !== undefined) { + return envs; + } const locators = this.getLocators(options); const promises = locators.map(async (provider) => provider.getInterpreters(resource)); locators.forEach((locator) => { diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts index 775e87ef20da..735c84c6f09b 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts @@ -13,7 +13,7 @@ import { IFileSystem, IPlatformService } from '../../../../common/platform/types import { IProcessServiceFactory } from '../../../../common/process/types'; import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory } from '../../../../common/types'; import { cache } from '../../../../common/utils/decorators'; -import { ICondaService, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../../../interpreter/contracts'; +import { IComponentAdapter, ICondaService, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../../../interpreter/contracts'; import { EnvironmentType, PythonEnvironment } from '../../../info'; import { CondaEnvironmentInfo, CondaInfo } from './conda'; import { parseCondaEnvFileContents } from './condaHelper'; @@ -49,6 +49,12 @@ export const CondaLocationsGlobWin = `{${condaGlobPathsForWindows.join(',')}}`; export const CondaGetEnvironmentPrefix = 'Outputting Environment Now...'; +// The parts of IComponentAdapter used here. +interface IComponent { + isCondaEnvironment(interpreterPath: string): Promise; + getCondaEnvironment(interpreterPath: string): Promise; +} + /** * A wrapper around a conda installation. */ @@ -66,6 +72,7 @@ export class CondaService implements ICondaService { @inject(IConfigurationService) private configService: IConfigurationService, @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IComponentAdapter) private readonly pyenvs: IComponent, @inject(IInterpreterLocatorService) @named(WINDOWS_REGISTRY_SERVICE) @optional() @@ -183,6 +190,10 @@ export class CondaService implements ICondaService { * @memberof CondaService */ public async isCondaEnvironment(interpreterPath: string): Promise { + const result = await this.pyenvs.isCondaEnvironment(interpreterPath); + if (result !== undefined) { + return result; + } const dir = path.dirname(interpreterPath); const { isWindows } = this.platform; const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta'); @@ -193,6 +204,10 @@ export class CondaService implements ICondaService { * Return (env name, interpreter filename) for the interpreter. */ public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> { + const found = await this.pyenvs.getCondaEnvironment(interpreterPath); + if (found !== undefined) { + return found; + } const isCondaEnv = await this.isCondaEnvironment(interpreterPath); if (!isCondaEnv) { return; diff --git a/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts b/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts index c00d8fc6b625..c67d13d9cd2a 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts @@ -28,7 +28,7 @@ export class InterpeterHashProviderFactory implements IInterpreterHashProviderFa ? options.pythonPath : this.configService.getSettings(options.resource).pythonPath; - return this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath) + return (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) ? this.windowsStoreHashProvider : this.hashProvider; } diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts index 0dabadf1fdf9..d72ad667ebcb 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts @@ -186,7 +186,7 @@ export class WindowsRegistryService extends CacheableLocatorService { // Give preference to what we have retrieved from getInterpreterInformation. version: details.version || parsePythonVersion(version), companyDisplayName: interpreterInfo.companyDisplayName, - envType: this.windowsStoreInterpreter.isWindowsStoreInterpreter(executablePath) + envType: (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(executablePath)) ? EnvironmentType.WindowsStore : EnvironmentType.Unknown, } as PythonEnvironment; diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts index 72fe2b82e3a9..4e04f0828c31 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts @@ -9,6 +9,7 @@ import { traceDecorators } from '../../../../common/logger'; import { IFileSystem } from '../../../../common/platform/types'; import { IPythonExecutionFactory } from '../../../../common/process/types'; import { IPersistentStateFactory } from '../../../../common/types'; +import { IComponentAdapter } from '../../../../interpreter/contracts'; import { IInterpreterHashProvider, IWindowsStoreInterpreter } from '../../../../interpreter/locators/types'; import { IServiceContainer } from '../../../../ioc/types'; @@ -31,6 +32,11 @@ export function isRestrictedWindowsStoreInterpreterPath(pythonPath: string): boo ); } +// The parts of IComponentAdapter used here. +interface IComponent { + isWindowsStoreInterpreter(pythonPath: string): Promise; +} + /** * The default location of Windows apps are `%ProgramFiles%\WindowsApps`. * (https://www.samlogic.net/articles/windows-8-windowsapps-folder.htm) @@ -60,6 +66,7 @@ export class WindowsStoreInterpreter implements IWindowsStoreInterpreter, IInter @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, @inject(IPersistentStateFactory) private readonly persistentFactory: IPersistentStateFactory, @inject(IFileSystem) private readonly fs: IFileSystem, + @inject(IComponentAdapter) private readonly pyenvs: IComponent ) {} /** @@ -69,7 +76,11 @@ export class WindowsStoreInterpreter implements IWindowsStoreInterpreter, IInter * @returns {boolean} * @memberof WindowsStoreInterpreter */ - public isWindowsStoreInterpreter(pythonPath: string): boolean { + public async isWindowsStoreInterpreter(pythonPath: string): Promise { + const result = await this.pyenvs.isWindowsStoreInterpreter(pythonPath); + if (result !== undefined) { + return result; + } const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\'); return ( pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\'.toUpperCase()) diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index 49abb879ccbf..fbeb6cc097a3 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -191,15 +191,6 @@ class ComponentAdapter implements IComponentAdapter { // IInterpreterService - // A result of `undefined` means "Fall back to the old code!" - public get hasInterpreters(): Promise { - if (!this.enabled) { - return Promise.resolve(undefined); - } - const iterator = this.api.iterEnvs(); - return iterator.next().then((res) => !res.done); - } - // We use the same getInterpreters() here as for IInterpreterLocatorService. // A result of `undefined` means "Fall back to the old code!" @@ -274,6 +265,15 @@ class ComponentAdapter implements IComponentAdapter { // IInterpreterLocatorService + // A result of `undefined` means "Fall back to the old code!" + public get hasInterpreters(): Promise { + if (!this.enabled) { + return Promise.resolve(undefined); + } + const iterator = this.api.iterEnvs(); + return iterator.next().then((res) => !res.done); + } + // A result of `undefined` means "Fall back to the old code!" public async getInterpreters( resource?: vscode.Uri, diff --git a/src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts b/src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts index 48bf504eab11..cac0a6db20bc 100644 --- a/src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts +++ b/src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts @@ -220,7 +220,7 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => { .verifiable(typemoq.Times.once()); helper .setup((i) => i.isMacDefaultPythonPath(typemoq.It.isAny())) - .returns(() => false) + .returns(() => Promise.resolve(false)) .verifiable(typemoq.Times.once()); const diagnostics = await diagnosticService.diagnose(undefined); @@ -251,7 +251,7 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => { .verifiable(typemoq.Times.once()); helper .setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(pythonPath))) - .returns(() => true) + .returns(() => Promise.resolve(true)) .verifiable(typemoq.Times.atLeastOnce()); const diagnostics = await diagnosticService.diagnose(undefined); @@ -291,11 +291,11 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => { .verifiable(typemoq.Times.once()); helper .setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(pythonPath))) - .returns(() => true) + .returns(() => Promise.resolve(true)) .verifiable(typemoq.Times.atLeastOnce()); helper .setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(nonMacStandardInterpreter))) - .returns(() => false) + .returns(() => Promise.resolve(false)) .verifiable(typemoq.Times.atLeastOnce()); interpreterService .setup((i) => i.getActiveInterpreter(typemoq.It.isAny())) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 28a0756fa2b1..d2f807c8c33f 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -217,7 +217,7 @@ suite('Process - PythonExecutionFactory', () => { when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(true); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(Promise.resolve(true)); const service = await factory.create({ resource }); diff --git a/src/test/interpreters/helpers.unit.test.ts b/src/test/interpreters/helpers.unit.test.ts index 481e6bb5ed31..1a664f5cb54b 100644 --- a/src/test/interpreters/helpers.unit.test.ts +++ b/src/test/interpreters/helpers.unit.test.ts @@ -8,6 +8,7 @@ import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, TextDocument, TextEditor, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; +import { IComponentAdapter } from '../../client/interpreter/contracts'; import { InterpreterHelper } from '../../client/interpreter/helpers'; import { IInterpreterHashProviderFactory } from '../../client/interpreter/locators/types'; import { IServiceContainer } from '../../client/ioc/types'; @@ -19,11 +20,13 @@ suite('Interpreters Display Helper', () => { let serviceContainer: TypeMoq.IMock; let helper: InterpreterHelper; let hashProviderFactory: TypeMoq.IMock; + let pyenvs: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); documentManager = TypeMoq.Mock.ofType(); hashProviderFactory = TypeMoq.Mock.ofType(); + pyenvs = TypeMoq.Mock.ofType(); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) @@ -32,7 +35,7 @@ suite('Interpreters Display Helper', () => { .setup((c) => c.get(TypeMoq.It.isValue(IDocumentManager))) .returns(() => documentManager.object); - helper = new InterpreterHelper(serviceContainer.object, hashProviderFactory.object); + helper = new InterpreterHelper(serviceContainer.object, hashProviderFactory.object, pyenvs.object); }); test('getActiveWorkspaceUri should return undefined if there are no workspaces', () => { workspaceService.setup((w) => w.workspaceFolders).returns(() => []); diff --git a/src/test/interpreters/interpreterService.unit.test.ts b/src/test/interpreters/interpreterService.unit.test.ts index eab8c0ae2664..df58a845e04f 100644 --- a/src/test/interpreters/interpreterService.unit.test.ts +++ b/src/test/interpreters/interpreterService.unit.test.ts @@ -37,6 +37,7 @@ import { } from '../../client/interpreter/autoSelection/types'; import { IPythonPathUpdaterServiceManager } from '../../client/interpreter/configuration/types'; import { + IComponentAdapter, IInterpreterDisplay, IInterpreterHelper, IInterpreterLocatorService, @@ -57,6 +58,7 @@ suite('Interpreters service', () => { let serviceManager: ServiceManager; let serviceContainer: ServiceContainer; let updater: TypeMoq.IMock; + let pyenvs: TypeMoq.IMock; let helper: TypeMoq.IMock; let locator: TypeMoq.IMock; let workspace: TypeMoq.IMock; @@ -81,6 +83,7 @@ suite('Interpreters service', () => { experimentsManager = TypeMoq.Mock.ofType(); interpreterPathService = TypeMoq.Mock.ofType(); updater = TypeMoq.Mock.ofType(); + pyenvs = TypeMoq.Mock.ofType(); helper = TypeMoq.Mock.ofType(); locator = TypeMoq.Mock.ofType(); workspace = TypeMoq.Mock.ofType(); @@ -169,7 +172,7 @@ suite('Interpreters service', () => { .returns(() => Promise.resolve(undefined)) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); await service.refresh(resource); interpreterDisplay.verifyAll(); @@ -181,7 +184,7 @@ suite('Interpreters service', () => { .returns(() => Promise.resolve([])) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); await service.getInterpreters(resource); locator.verifyAll(); @@ -189,7 +192,7 @@ suite('Interpreters service', () => { }); test('Changes to active document should invoke interpreter.refresh method', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const documentManager = TypeMoq.Mock.ofType(); experimentsManager.setup((e) => e.inExperiment(DeprecatePythonPath.experiment)).returns(() => false); @@ -220,7 +223,7 @@ suite('Interpreters service', () => { }); test('If there is no active document then interpreter.refresh should not be invoked', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const documentManager = TypeMoq.Mock.ofType(); experimentsManager.setup((e) => e.inExperiment(DeprecatePythonPath.experiment)).returns(() => false); @@ -246,7 +249,7 @@ suite('Interpreters service', () => { }); test('If user belongs to Deprecate Pythonpath experiment, register the correct handler', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const documentManager = TypeMoq.Mock.ofType(); experimentsManager.setup((e) => e.inExperiment(DeprecatePythonPath.experiment)).returns(() => true); @@ -293,7 +296,7 @@ suite('Interpreters service', () => { }); test('If stored setting is an empty string, refresh the interpreter display', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const resource = Uri.parse('a'); service._pythonPathSetting = ''; configService.reset(); @@ -307,7 +310,7 @@ suite('Interpreters service', () => { }); test('If stored setting is not equal to current interpreter path setting, refresh the interpreter display', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const resource = Uri.parse('a'); service._pythonPathSetting = 'stored setting'; configService.reset(); @@ -321,7 +324,7 @@ suite('Interpreters service', () => { }); test('If stored setting is equal to current interpreter path setting, do not refresh the interpreter display', async () => { - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const resource = Uri.parse('a'); service._pythonPathSetting = 'setting'; configService.reset(); @@ -340,7 +343,7 @@ suite('Interpreters service', () => { [undefined, Uri.file('some workspace')].forEach((resource) => { test(`Ensure undefined is returned if we're unable to retrieve interpreter info (Resource is ${resource})`, async () => { const pythonPath = 'SOME VALUE'; - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); locator .setup((l) => l.getInterpreters(TypeMoq.It.isValue(resource), TypeMoq.It.isAny())) .returns(() => Promise.resolve([])) @@ -394,7 +397,7 @@ suite('Interpreters service', () => { }) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const displayName = await service.getDisplayName(interpreterInfo, undefined); expect(displayName).to.equal(expectedDisplayName); @@ -426,7 +429,7 @@ suite('Interpreters service', () => { }) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const displayName = await service.getDisplayName(interpreterInfo, undefined).catch(() => ''); expect(displayName).to.not.equal(expectedDisplayName); @@ -508,7 +511,8 @@ suite('Interpreters service', () => { const service = new InterpreterService( serviceContainer, - hashProviderFactory.object + hashProviderFactory.object, + pyenvs.object ); const expectedDisplayName = buildDisplayName(interpreterInfo); @@ -606,7 +610,7 @@ suite('Interpreters service', () => { .returns(() => state.object) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const store = await service.getInterpreterCache(pythonPath); @@ -651,7 +655,7 @@ suite('Interpreters service', () => { .returns(() => state.object) .verifiable(TypeMoq.Times.once()); - const service = new InterpreterService(serviceContainer, hashProviderFactory.object); + const service = new InterpreterService(serviceContainer, hashProviderFactory.object, pyenvs.object); const store = await service.getInterpreterCache(pythonPath); diff --git a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts index 09375027bdd8..44048fd185fa 100644 --- a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts @@ -14,7 +14,7 @@ import { IProcessService, IProcessServiceFactory } from '../../../../client/comm import { ITerminalActivationCommandProvider } from '../../../../client/common/terminal/types'; import { IConfigurationService, IPersistentStateFactory, IPythonSettings } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; -import { IInterpreterLocatorService, IInterpreterService } from '../../../../client/interpreter/contracts'; +import { IComponentAdapter, IInterpreterLocatorService, IInterpreterService } from '../../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../../client/ioc/types'; import { CondaService } from '../../../../client/pythonEnvironments/discovery/locators/services/condaService'; import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; @@ -39,6 +39,7 @@ suite('Interpreters Conda Service', () => { let processService: TypeMoq.IMock; let platformService: TypeMoq.IMock; let condaService: CondaService; + let pyenvs: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; let config: TypeMoq.IMock; let settings: TypeMoq.IMock; @@ -59,6 +60,7 @@ suite('Interpreters Conda Service', () => { persistentStateFactory = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); registryInterpreterLocatorService = TypeMoq.Mock.ofType(); + pyenvs = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); config = TypeMoq.Mock.ofType(); @@ -123,6 +125,7 @@ suite('Interpreters Conda Service', () => { config.object, disposableRegistry, workspaceService.object, + pyenvs.object, registryInterpreterLocatorService.object, ); }); @@ -606,6 +609,7 @@ suite('Interpreters Conda Service', () => { config.object, disposableRegistry, workspaceService.object, + pyenvs.object, ); const result = await condaSrv.getCondaFile(); diff --git a/src/test/pythonEnvironments/discovery/locators/hasProviderFactory.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/hasProviderFactory.unit.test.ts index 8960905eb1f0..9a0923a05ffb 100644 --- a/src/test/pythonEnvironments/discovery/locators/hasProviderFactory.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/hasProviderFactory.unit.test.ts @@ -40,7 +40,7 @@ suite('Interpretersx - Interpreter Hash Provider Factory', () => { }); test('When provided python path is not a window store interpreter return standard hash provider', async () => { const pythonPath = 'NonWindowsInterpreterPath'; - when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(false); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(Promise.resolve(false)); const provider = await factory.create({ pythonPath }); @@ -49,7 +49,7 @@ suite('Interpretersx - Interpreter Hash Provider Factory', () => { }); test('When provided python path is a windows store interpreter return windows store hash provider', async () => { const pythonPath = 'NonWindowsInterpreterPath'; - when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(true); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(Promise.resolve(true)); const provider = await factory.create({ pythonPath }); @@ -60,7 +60,7 @@ suite('Interpretersx - Interpreter Hash Provider Factory', () => { const pythonPath = 'NonWindowsInterpreterPath'; const resource = Uri.file('1'); when(configService.getSettings(resource)).thenReturn({ pythonPath } as any); - when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(false); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(Promise.resolve(false)); const provider = await factory.create({ resource }); @@ -71,7 +71,7 @@ suite('Interpretersx - Interpreter Hash Provider Factory', () => { const pythonPath = 'NonWindowsInterpreterPath'; const resource = Uri.file('1'); when(configService.getSettings(resource)).thenReturn({ pythonPath } as any); - when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(true); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(Promise.resolve(true)); const provider = await factory.create({ resource }); diff --git a/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts index 1f597d9782e1..248962b43ccb 100644 --- a/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts @@ -76,7 +76,7 @@ suite('Interpreters - Locators Helper', () => { // Treat 'mac' as as mac interpreter. interpreterServiceHelper .setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isValue(interpreter.path))) - .returns(() => name === 'mac') + .returns(() => Promise.resolve(name === 'mac')) .verifiable(TypeMoq.Times.never()); }); @@ -93,7 +93,9 @@ suite('Interpreters - Locators Helper', () => { }); getNamesAndValues(OS).forEach((os) => { test(`Ensure duplicates are removed (same version and same interpreter directory on ${os.name})`, async () => { - interpreterServiceHelper.setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isAny())).returns(() => false); + interpreterServiceHelper + .setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(false)); platform.setup((p) => p.isWindows).returns(() => os.value === OS.Windows); platform.setup((p) => p.isLinux).returns(() => os.value === OS.Linux); platform.setup((p) => p.isMac).returns(() => os.value === OS.Mac); @@ -158,7 +160,9 @@ suite('Interpreters - Locators Helper', () => { }); getNamesAndValues(OS).forEach((os) => { test(`Ensure interpreter types are identified from other locators (${os.name})`, async () => { - interpreterServiceHelper.setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isAny())).returns(() => false); + interpreterServiceHelper + .setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(false)); platform.setup((p) => p.isWindows).returns(() => os.value === OS.Windows); platform.setup((p) => p.isLinux).returns(() => os.value === OS.Linux); platform.setup((p) => p.isMac).returns(() => os.value === OS.Mac); diff --git a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts index 53221858a193..2480aeed31d8 100644 --- a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts @@ -19,6 +19,7 @@ import { CONDA_ENV_SERVICE, CURRENT_PATH_SERVICE, GLOBAL_VIRTUAL_ENV_SERVICE, + IComponentAdapter, IInterpreterLocatorHelper, IInterpreterLocatorService, KNOWN_PATH_SERVICE, @@ -839,18 +840,21 @@ suite('Interpreters - Locators Index', () => { let serviceContainer: TypeMoq.IMock; let platformSvc: TypeMoq.IMock; let helper: TypeMoq.IMock; + let pyenvs: TypeMoq.IMock; let locator: IInterpreterLocatorService; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); platformSvc = TypeMoq.Mock.ofType(); helper = TypeMoq.Mock.ofType(); + pyenvs = TypeMoq.Mock.ofType(); serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []); serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformSvc.object); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))).returns(() => pyenvs.object); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterLocatorHelper))) .returns(() => helper.object); - locator = new PythonInterpreterLocatorService(serviceContainer.object); + locator = new PythonInterpreterLocatorService(serviceContainer.object, pyenvs.object); }); [undefined, Uri.file('Something')].forEach((resource) => { getNamesAndValues(OSType).forEach((osType) => { diff --git a/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts index b067f6ebd73a..75d9860f9680 100644 --- a/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts @@ -32,7 +32,9 @@ suite('Interpreters from Windows Registry (unit)', () => { fs = TypeMoq.Mock.ofType(); windowsStoreInterpreter = TypeMoq.Mock.ofType(); windowsStoreInterpreter.setup((w) => w.isHiddenInterpreter(TypeMoq.It.isAny())).returns(() => false); - windowsStoreInterpreter.setup((w) => w.isWindowsStoreInterpreter(TypeMoq.It.isAny())).returns(() => false); + windowsStoreInterpreter + .setup((w) => w.isWindowsStoreInterpreter(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(false)); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IPersistentStateFactory))) .returns(() => stateFactory.object); @@ -331,7 +333,7 @@ suite('Interpreters from Windows Registry (unit)', () => { .verifiable(TypeMoq.Times.atLeastOnce()); windowsStoreInterpreter .setup((w) => w.isWindowsStoreInterpreter(TypeMoq.It.isValue(expectedPythonPath))) - .returns(() => true) + .returns(() => Promise.resolve(true)) .verifiable(TypeMoq.Times.atLeastOnce()); const interpreters = await winRegistry.getInterpreters(); diff --git a/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts index 95c89f86f723..6eb5e63597f2 100644 --- a/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts @@ -19,13 +19,22 @@ import { ServiceContainer } from '../../../../client/ioc/container'; import { IServiceContainer } from '../../../../client/ioc/types'; import { WindowsStoreInterpreter } from '../../../../client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; +// We use this for mocking. +class ComponentAdapter { + public async isWindowsStoreInterpreter(_pythonPath: string): Promise { + return undefined; + } +} + suite('Interpreters - Windows Store Interpreter', () => { let windowsStoreInterpreter: WindowsStoreInterpreter; + let pyenvs: ComponentAdapter; let fs: IFileSystem; let persistanceStateFactory: IPersistentStateFactory; let executionFactory: IPythonExecutionFactory; let serviceContainer: IServiceContainer; setup(() => { + pyenvs = mock(ComponentAdapter); fs = mock(FileSystem); persistanceStateFactory = mock(PersistentStateFactory); executionFactory = mock(PythonExecutionFactory); @@ -33,10 +42,12 @@ suite('Interpreters - Windows Store Interpreter', () => { when(serviceContainer.get(IPythonExecutionFactory)).thenReturn( instance(executionFactory), ); + when(pyenvs.isWindowsStoreInterpreter(anything())).thenReturn(Promise.resolve(undefined)); windowsStoreInterpreter = new WindowsStoreInterpreter( instance(serviceContainer), instance(persistanceStateFactory), instance(fs), + instance(pyenvs) ); }); const windowsStoreInterpreters = [ @@ -51,23 +62,23 @@ suite('Interpreters - Windows Store Interpreter', () => { 'C:\\microsoft\\WindowsApps\\PythonSoftwareFoundation\\Something\\Python.exe', ]; for (const interpreter of windowsStoreInterpreters) { - test(`${interpreter} must be identified as a windows store interpter`, () => { - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter)).to.equal(true, 'Must be true'); + test(`${interpreter} must be identified as a windows store interpter`, async () => { + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter)).to.equal(true, 'Must be true'); }); - test(`${interpreter.toLowerCase()} must be identified as a windows store interpter (ignoring case)`, () => { - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toLowerCase())).to.equal( + test(`${interpreter.toLowerCase()} must be identified as a windows store interpter (ignoring case)`, async () => { + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toLowerCase())).to.equal( true, 'Must be true', ); - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal( + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal( true, 'Must be true', ); }); test(`D${interpreter.substring( 1, - )} must be identified as a windows store interpter (ignoring driver letter)`, () => { - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(`D${interpreter.substring(1)}`)).to.equal( + )} must be identified as a windows store interpter (ignoring driver letter)`, async () => { + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(`D${interpreter.substring(1)}`)).to.equal( true, 'Must be true', ); @@ -75,8 +86,8 @@ suite('Interpreters - Windows Store Interpreter', () => { test(`${interpreter.replace( /\\/g, '/', - )} must be identified as a windows store interpter (ignoring path separator)`, () => { - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.replace(/\\/g, '/'))).to.equal( + )} must be identified as a windows store interpter (ignoring path separator)`, async () => { + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.replace(/\\/g, '/'))).to.equal( true, 'Must be true', ); @@ -97,14 +108,14 @@ suite('Interpreters - Windows Store Interpreter', () => { 'C:\\Apps\\Python.exe', ]; for (const interpreter of nonWindowsStoreInterpreters) { - test(`${interpreter} must not be identified as a windows store interpter`, () => { + test(`${interpreter} must not be identified as a windows store interpter`, async () => { expect(windowsStoreInterpreter.isHiddenInterpreter(interpreter)).to.equal(false, 'Must be false'); expect(windowsStoreInterpreter.isHiddenInterpreter(interpreter.replace(/\\/g, '/'))).to.equal( false, 'Must be false', ); - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter)).to.equal(false, 'Must be false'); - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.replace(/\\/g, '/'))).to.equal( + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter)).to.equal(false, 'Must be false'); + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.replace(/\\/g, '/'))).to.equal( false, 'Must be false', ); @@ -112,11 +123,11 @@ suite('Interpreters - Windows Store Interpreter', () => { false, 'Must be false', ); - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal( + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal( false, 'Must be false', ); - expect(windowsStoreInterpreter.isWindowsStoreInterpreter(`D${interpreter.substring(1)}`)).to.equal( + expect(await windowsStoreInterpreter.isWindowsStoreInterpreter(`D${interpreter.substring(1)}`)).to.equal( false, 'Must be false', );