diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b094d6b3073..383e21d49e04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,12 +211,6 @@ jobs: & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath & $condaExecPath init --all - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "~/test_env3" -y python - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION run: | echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index f2cb81d5e6ec..dac58b3eb608 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -191,12 +191,6 @@ jobs: & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath & $condaExecPath init --all - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "~/test_env3" -y python - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION run: | echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV @@ -458,12 +452,6 @@ jobs: & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath & $condaExecPath init --all - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "~/test_env3" -y python - - name: Run TypeScript unit tests run: npm run test:unittests:cover diff --git a/news/3 Code Health/17795.md b/news/3 Code Health/17795.md new file mode 100644 index 000000000000..5b90a52bbf2b --- /dev/null +++ b/news/3 Code Health/17795.md @@ -0,0 +1 @@ +Remove old discovery code and discovery experiments. diff --git a/package.json b/package.json index a0ed5b526513..1f0d42887d37 100644 --- a/package.json +++ b/package.json @@ -616,8 +616,6 @@ "enum": [ "All", "pythonDeprecatePythonPath", - "pythonDiscoveryModule", - "pythonDiscoveryModuleWithoutWatcher", "pythonSurveyNotification", "pythonTensorboardExperiment", "pythonRunFailedTestsButtonDisplayed", @@ -635,8 +633,6 @@ "enum": [ "All", "pythonDeprecatePythonPath", - "pythonDiscoveryModule", - "pythonDiscoveryModuleWithoutWatcher", "pythonSurveyNotification", "pythonTensorboardExperiment", "pythonRunFailedTestsButtonDisplayed", diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index ca24e0496df5..54470e7b2ad0 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -22,12 +22,6 @@ export enum NativeTensorBoard { experiment = 'pythonTensorboardExperiment', } -// Experiment to control which environment discovery mechanism can be used -export enum DiscoveryVariants { - discoverWithFileWatching = 'pythonDiscoveryModule', - discoveryWithoutFileWatching = 'pythonDiscoveryModuleWithoutWatcher', -} - // Feature gate to control whether we install the PyTorch profiler package // torch.profiler release is being delayed till end of March. This allows us // to turn on the profiler plugin install functionality between releases diff --git a/src/client/common/experiments/service.ts b/src/client/common/experiments/service.ts index 3d912e83ab6b..48f09332cbb8 100644 --- a/src/client/common/experiments/service.ts +++ b/src/client/common/experiments/service.ts @@ -12,7 +12,6 @@ import { IApplicationEnvironment, IWorkspaceService } from '../application/types import { PVSC_EXTENSION_ID, STANDARD_OUTPUT_CHANNEL } from '../constants'; import { GLOBAL_MEMENTO, IExperimentService, IMemento, IOutputChannel } from '../types'; import { Experiments } from '../utils/localize'; -import { DiscoveryVariants } from './groups'; import { ExperimentationTelemetry } from './telemetry'; const EXP_MEMENTO_KEY = 'VSCode.ABExp.FeatureData'; @@ -109,11 +108,6 @@ export class ExperimentService implements IExperimentService { } public inExperimentSync(experiment: string): boolean { - if (experiment === DiscoveryVariants.discoveryWithoutFileWatching) { - // Enable discovery experiment for all users. - return true; - } - if (!this.experimentationService) { return false; } diff --git a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts index 4e271d592784..b764819d9de8 100644 --- a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts @@ -5,11 +5,44 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; import { IServiceContainer } from '../../../ioc/types'; -import { getVenvExecutableFinder } from '../../../pythonEnvironments/discovery/subenv'; import { IFileSystem } from '../../platform/types'; import { IConfigurationService } from '../../types'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; +type ExecutableFinderFunc = (python: string) => Promise; + +/** + * Build an "executable finder" function that identifies venv environments. + * + * @param basename - the venv name or names to look for + * @param pathDirname - typically `path.dirname` + * @param pathJoin - typically `path.join` + * @param fileExists - typically `fs.exists` + */ + +function getVenvExecutableFinder( + basename: string | string[], + // + pathDirname: (filename: string) => string, + pathJoin: (...parts: string[]) => string, + // + fileExists: (n: string) => Promise, +): ExecutableFinderFunc { + const basenames = typeof basename === 'string' ? [basename] : basename; + return async (python: string) => { + // Generated scripts are found in the same directory as the interpreter. + const binDir = pathDirname(python); + for (const name of basenames) { + const filename = pathJoin(binDir, name); + if (await fileExists(filename)) { + return filename; + } + } + // No matches so return undefined. + return undefined; + }; +} + @injectable() abstract class BaseActivationCommandProvider implements ITerminalActivationCommandProvider { constructor(@inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer) {} diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 651c793fd185..65e9588864f7 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -1,37 +1,12 @@ import { SemVer } from 'semver'; import { CodeLensProvider, ConfigurationTarget, Disposable, Event, TextDocument, Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../activation/types'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { Resource } from '../common/types'; import { PythonEnvSource } from '../pythonEnvironments/base/info'; import { PythonLocatorQuery } from '../pythonEnvironments/base/locator'; -import { CondaEnvironmentInfo, CondaInfo } from '../pythonEnvironments/common/environmentManagers/conda'; +import { CondaEnvironmentInfo } from '../pythonEnvironments/common/environmentManagers/conda'; import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; -export const INTERPRETER_LOCATOR_SERVICE = 'IInterpreterLocatorService'; -export const WINDOWS_REGISTRY_SERVICE = 'WindowsRegistryService'; -export const CONDA_ENV_FILE_SERVICE = 'CondaEnvFileService'; -export const CONDA_ENV_SERVICE = 'CondaEnvService'; -export const CURRENT_PATH_SERVICE = 'CurrentPathService'; -export const KNOWN_PATH_SERVICE = 'KnownPathsService'; -export const GLOBAL_VIRTUAL_ENV_SERVICE = 'VirtualEnvService'; -export const WORKSPACE_VIRTUAL_ENV_SERVICE = 'WorkspaceVirtualEnvService'; -export const PIPENV_SERVICE = 'PipEnvService'; -export const IInterpreterVersionService = Symbol('IInterpreterVersionService'); -export interface IInterpreterVersionService { - getVersion(pythonPath: string, defaultValue: string): Promise; - getPipVersion(pythonPath: string): Promise; -} - -export const IKnownSearchPathsForInterpreters = Symbol('IKnownSearchPathsForInterpreters'); -export interface IKnownSearchPathsForInterpreters { - getSearchPaths(): string[]; -} -export const IVirtualEnvironmentsSearchPathProvider = Symbol('IVirtualEnvironmentsSearchPathProvider'); -export interface IVirtualEnvironmentsSearchPathProvider { - getSearchPaths(resource?: Uri): Promise; -} - export type PythonEnvironmentsChangedEvent = { type?: FileChangeType; resource?: Uri; @@ -74,15 +49,6 @@ export interface IComponentAdapter { isWindowsStoreInterpreter(pythonPath: string): Promise; } -export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService'); - -export interface IInterpreterLocatorService extends Disposable { - readonly onLocating: Event>; - readonly hasInterpreters: Promise; - didTriggerInterpreterSuggestions?: boolean; - getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise; -} - export const ICondaService = Symbol('ICondaService'); /** * Interface carries the properties which are not available via the discovery component interface. @@ -94,20 +60,6 @@ export interface ICondaService { getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise; } -export const ICondaLocatorService = Symbol('ICondaLocatorService'); -/** - * @deprecated Use the new discovery component when in experiment, use this otherwise. - */ -export interface ICondaLocatorService { - readonly condaEnvironmentsFile: string | undefined; - getCondaFile(): Promise; - getCondaInfo(): Promise; - getCondaEnvironments(ignoreCache: boolean): Promise; - getInterpreterPath(condaEnvironmentPath: string): string; - isCondaEnvironment(interpreterPath: string): Promise; - getCondaEnvironment(interpreterPath: string): Promise; -} - export const IInterpreterService = Symbol('IInterpreterService'); export interface IInterpreterService { readonly onRefreshStart: Event; @@ -119,7 +71,7 @@ export interface IInterpreterService { onDidChangeInterpreterInformation: Event; hasInterpreters(filter?: (e: PythonEnvironment) => Promise): Promise; getInterpreters(resource?: Uri): PythonEnvironment[]; - getAllInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise; + getAllInterpreters(resource?: Uri): Promise; getActiveInterpreter(resource?: Uri): Promise; getInterpreterDetails(pythonPath: string, resoure?: Uri): Promise; refresh(resource: Resource): Promise; @@ -146,33 +98,6 @@ export interface IInterpreterHelper { getBestInterpreter(interpreters?: PythonEnvironment[]): PythonEnvironment | undefined; } -export const IPipEnvService = Symbol('IPipEnvService'); -export interface IPipEnvService extends IInterpreterLocatorService { - executable: string; - isRelatedPipEnvironment(dir: string, pythonPath: string): Promise; -} - -export const IInterpreterLocatorHelper = Symbol('IInterpreterLocatorHelper'); -export interface IInterpreterLocatorHelper { - mergeInterpreters(interpreters: PythonEnvironment[]): Promise; -} - -export const IInterpreterWatcher = Symbol('IInterpreterWatcher'); -export interface IInterpreterWatcher { - onDidCreate: Event; -} - -export const IInterpreterWatcherBuilder = Symbol('IInterpreterWatcherBuilder'); -export interface IInterpreterWatcherBuilder { - getWorkspaceVirtualEnvInterpreterWatcher(resource: Resource): Promise; -} - -export const IInterpreterLocatorProgressService = Symbol('IInterpreterLocatorProgressService'); -export interface IInterpreterLocatorProgressService extends IExtensionSingleActivationService { - readonly onRefreshing: Event; - readonly onRefreshed: Event; -} - export const IInterpreterStatusbarVisibilityFilter = Symbol('IInterpreterStatusbarVisibilityFilter'); /** * Implement this interface to control the visibility of the interpreter statusbar. @@ -186,5 +111,3 @@ export type WorkspacePythonPath = { folderUri: Uri; configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder; }; - -export type GetInterpreterOptions = { ignoreCache?: boolean; onSuggestion?: boolean }; diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts deleted file mode 100644 index 29167cd379b6..000000000000 --- a/src/client/interpreter/interpreterVersion.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { inject, injectable } from 'inversify'; -import '../common/extensions'; -import * as internalPython from '../common/process/internal/python'; -import { IProcessServiceFactory } from '../common/process/types'; -import { getPythonVersion } from '../pythonEnvironments/info/pythonVersion'; -import { IInterpreterVersionService } from './contracts'; - -const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)?'; - -@injectable() -export class InterpreterVersionService implements IInterpreterVersionService { - constructor(@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory) {} - - public async getVersion(pythonPath: string, defaultValue: string): Promise { - const processService = await this.processServiceFactory.create(); - return getPythonVersion(pythonPath, defaultValue, (cmd, args) => - processService.exec(cmd, args, { mergeStdOutErr: true }), - ); - } - - public async getPipVersion(pythonPath: string): Promise { - const [args, parse] = internalPython.getModuleVersion('pip'); - const processService = await this.processServiceFactory.create(); - const output = await processService.exec(pythonPath, args, { mergeStdOutErr: true }); - const version = parse(output.stdout); - if (version.length > 0) { - // Here's a sample output: - // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). - const re = new RegExp(PIP_VERSION_REGEX, 'g'); - const matches = re.exec(version); - if (matches && matches.length > 0) { - return matches[0].trim(); - } - } - throw new Error(`Unable to determine pip version from output '${output.stdout}'`); - } -} diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index c7aa5cf6515f..de27e1c914d7 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -23,19 +23,12 @@ import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager, } from './configuration/types'; -import { - IInterpreterDisplay, - IInterpreterHelper, - IInterpreterService, - IInterpreterVersionService, - IShebangCodeLensProvider, -} from './contracts'; +import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, IShebangCodeLensProvider } from './contracts'; import { InterpreterDisplay } from './display'; import { InterpreterLocatorProgressStatubarHandler } from './display/progressDisplay'; import { ShebangCodeLensProvider } from './display/shebangCodeLensProvider'; import { InterpreterHelper } from './helpers'; import { InterpreterService } from './interpreterService'; -import { InterpreterVersionService } from './interpreterVersion'; import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; @@ -62,8 +55,6 @@ export function registerInterpreterTypes(serviceManager: IServiceManager): void serviceManager.addSingleton(IExtensionActivationService, VirtualEnvironmentPrompt); - serviceManager.addSingleton(IInterpreterVersionService, InterpreterVersionService); - serviceManager.addSingleton(IInterpreterService, InterpreterService); serviceManager.addSingleton(IInterpreterDisplay, InterpreterDisplay); diff --git a/src/client/interpreter/virtualEnvs/index.ts b/src/client/interpreter/virtualEnvs/index.ts deleted file mode 100644 index 5c59a19de4ec..000000000000 --- a/src/client/interpreter/virtualEnvs/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IProcessServiceFactory } from '../../common/process/types'; -import { getAllScripts as getNonWindowsScripts } from '../../common/terminal/environmentActivationProviders/bash'; -import { getAllScripts as getWindowsScripts } from '../../common/terminal/environmentActivationProviders/commandPrompt'; -import { ICurrentProcess, IPathUtils } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import * as globalenvs from '../../pythonEnvironments/discovery/globalenv'; -import * as subenvs from '../../pythonEnvironments/discovery/subenv'; -import { EnvironmentType } from '../../pythonEnvironments/info'; -import { IInterpreterLocatorService, IPipEnvService, PIPENV_SERVICE } from '../contracts'; -import { IVirtualEnvironmentManager } from './types'; - -@injectable() -export class VirtualEnvironmentManager implements IVirtualEnvironmentManager { - private processServiceFactory: IProcessServiceFactory; - private pipEnvService: IPipEnvService; - private fs: IFileSystem; - private workspaceService: IWorkspaceService; - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { - this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); - this.fs = serviceContainer.get(IFileSystem); - this.pipEnvService = serviceContainer.get( - IInterpreterLocatorService, - PIPENV_SERVICE, - ) as IPipEnvService; - this.workspaceService = serviceContainer.get(IWorkspaceService); - } - - public async getEnvironmentName(pythonPath: string, resource?: Uri): Promise { - const finders = subenvs.getNameFinders( - await this.getWorkspaceRoot(resource), - path.dirname, - path.basename, - // We use a closure on "this". - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p), - ); - return (await subenvs.getName(pythonPath, finders)) || ''; - } - - public async getEnvironmentType(pythonPath: string, resource?: Uri): Promise { - const pathUtils = this.serviceContainer.get(IPathUtils); - const plat = this.serviceContainer.get(IPlatformService); - const candidates = plat.isWindows ? getWindowsScripts(path.join) : getNonWindowsScripts(); - const finders = subenvs.getTypeFinders( - pathUtils.home, - candidates, - path.sep, - path.join, - path.dirname, - () => this.getWorkspaceRoot(resource), - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p), - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - (n: string) => this.fs.fileExists(n), - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - }, - ); - return (await subenvs.getType(pythonPath, finders)) || EnvironmentType.Unknown; - } - - public async isVenvEnvironment(pythonPath: string) { - const find = subenvs.getVenvTypeFinder( - path.dirname, - path.join, - // We use a closure on "this". - (n: string) => this.fs.fileExists(n), - ); - return (await find(pythonPath)) === EnvironmentType.Venv; - } - - public async isPyEnvEnvironment(pythonPath: string, resource?: Uri) { - const pathUtils = this.serviceContainer.get(IPathUtils); - const find = globalenvs.getPyenvTypeFinder( - pathUtils.home, - path.sep, - path.join, - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - }, - ); - return (await find(pythonPath)) === EnvironmentType.Pyenv; - } - - public async isPipEnvironment(pythonPath: string, resource?: Uri) { - const find = subenvs.getPipenvTypeFinder( - () => this.getWorkspaceRoot(resource), - // We use a closure on "this". - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p), - ); - return (await find(pythonPath)) === EnvironmentType.Pipenv; - } - - public async getPyEnvRoot(resource?: Uri): Promise { - const pathUtils = this.serviceContainer.get(IPathUtils); - const find = globalenvs.getPyenvRootFinder( - pathUtils.home, - path.join, - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - }, - ); - return find(); - } - - public async isVirtualEnvironment(pythonPath: string) { - const plat = this.serviceContainer.get(IPlatformService); - const candidates = plat.isWindows ? getWindowsScripts(path.join) : getNonWindowsScripts(); - const find = subenvs.getVirtualenvTypeFinder( - candidates, - path.dirname, - path.join, - // We use a closure on "this". - (n: string) => this.fs.fileExists(n), - ); - return (await find(pythonPath)) === EnvironmentType.VirtualEnv; - } - - private async getWorkspaceRoot(resource?: Uri): Promise { - const defaultWorkspaceUri = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders![0].uri - : undefined; - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - const uri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri; - return uri ? uri.fsPath : undefined; - } -} diff --git a/src/client/interpreter/virtualEnvs/types.ts b/src/client/interpreter/virtualEnvs/types.ts deleted file mode 100644 index 3781e4926d89..000000000000 --- a/src/client/interpreter/virtualEnvs/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Uri } from 'vscode'; -import { EnvironmentType } from '../../pythonEnvironments/info'; -export const IVirtualEnvironmentManager = Symbol('VirtualEnvironmentManager'); -export interface IVirtualEnvironmentManager { - getEnvironmentName(pythonPath: string, resource?: Uri): Promise; - getEnvironmentType(pythonPath: string, resource?: Uri): Promise; - getPyEnvRoot(resource?: Uri): Promise; -} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 7181ec975eea..c78ee7c92953 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -4,13 +4,11 @@ import * as fs from 'fs'; import * as path from 'path'; import { Uri } from 'vscode'; -import { DiscoveryVariants } from '../../../../common/experiments/groups'; import { traceError, traceVerbose } from '../../../../common/logger'; import { FileChangeType } from '../../../../common/platform/fileSystemWatcher'; import { sleep } from '../../../../common/utils/async'; import { logError } from '../../../../logging'; import { getEnvironmentDirFromPath } from '../../../common/commonUtils'; -import { inExperiment } from '../../../common/externalDependencies'; import { PythonEnvStructure, resolvePythonExeGlobs, @@ -91,12 +89,8 @@ export abstract class FSWatchingLocator extends LazyResourceB protected async initWatchers(): Promise { // Enable all workspace watchers. if (this.watcherKind === FSWatcherKind.Global) { - // Enable global watchers only if the experiment allows it. - const enableGlobalWatchers = await inExperiment(DiscoveryVariants.discoverWithFileWatching); - if (!enableGlobalWatchers) { - traceVerbose('Watcher disabled'); - return; - } + // Do not allow global watchers for now + return; } // Start the FS watchers. diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts b/src/client/pythonEnvironments/common/environmentManagers/condaService.ts similarity index 83% rename from src/client/pythonEnvironments/discovery/locators/services/condaService.ts rename to src/client/pythonEnvironments/common/environmentManagers/condaService.ts index a8b80b3d6906..43f896c08994 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts +++ b/src/client/pythonEnvironments/common/environmentManagers/condaService.ts @@ -2,17 +2,17 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { parse, SemVer } from 'semver'; import { ConfigurationChangeEvent, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { traceDecorators, traceWarning } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService, IDisposableRegistry } from '../../../../common/types'; -import { cache } from '../../../../common/utils/decorators'; -import { ICondaService } from '../../../../interpreter/contracts'; -import { Conda, CondaInfo } from '../../../common/environmentManagers/conda'; +import { IWorkspaceService } from '../../../common/application/types'; +import { traceDecorators, traceWarning } from '../../../common/logger'; +import { IFileSystem, IPlatformService } from '../../../common/platform/types'; +import { IProcessServiceFactory } from '../../../common/process/types'; +import { IDisposableRegistry } from '../../../common/types'; +import { cache } from '../../../common/utils/decorators'; +import { ICondaService } from '../../../interpreter/contracts'; +import { Conda, CondaInfo } from './conda'; /** - * A wrapper around a conda installation. + * Injectable version of Conda utility. */ @injectable() export class CondaService implements ICondaService { @@ -24,7 +24,6 @@ export class CondaService implements ICondaService { @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, @inject(IPlatformService) private platform: IPlatformService, @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IConfigurationService) private readonly configService: IConfigurationService, @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, ) { @@ -36,7 +35,7 @@ export class CondaService implements ICondaService { */ public async getCondaFile(): Promise { if (!this.condaFile) { - this.condaFile = this.getCondaFileImpl(); + this.condaFile = Conda.getConda().then((conda) => conda?.command ?? 'conda'); } return this.condaFile; } @@ -147,19 +146,6 @@ export class CondaService implements ICondaService { return conda?.getInfo(); } - /** - * Return the path to the "conda file", if there is one (in known locations). - */ - private async getCondaFileImpl(): Promise { - const settings = this.configService.getSettings(); - const setting = settings.condaPath; - if (setting && setting !== '') { - return setting; - } - const conda = await Conda.getConda(); - return conda?.command ?? 'conda'; - } - private addCondaPathChangedHandler() { const disposable = this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); this.disposableRegistry.push(disposable); diff --git a/src/client/pythonEnvironments/common/externalDependencies.ts b/src/client/pythonEnvironments/common/externalDependencies.ts index 25a18f3a3d00..16769ba300df 100644 --- a/src/client/pythonEnvironments/common/externalDependencies.ts +++ b/src/client/pythonEnvironments/common/externalDependencies.ts @@ -5,7 +5,7 @@ import * as fsapi from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; import { ExecutionResult, IProcessServiceFactory, ShellOptions, SpawnOptions } from '../../common/process/types'; -import { IExperimentService, IDisposable, IConfigurationService } from '../../common/types'; +import { IDisposable, IConfigurationService } from '../../common/types'; import { chain, iterable } from '../../common/utils/async'; import { normalizeFilename } from '../../common/utils/filesystem'; import { getOSType, OSType } from '../../common/utils/platform'; @@ -159,8 +159,3 @@ export function onDidChangePythonSetting(name: string, callback: () => void): ID } }); } - -export function inExperiment(experiment: string): Promise { - const experimentService = internalServiceContainer.get(IExperimentService); - return experimentService.inExperiment(experiment); -} diff --git a/src/client/pythonEnvironments/discovery/globalenv.ts b/src/client/pythonEnvironments/discovery/globalenv.ts deleted file mode 100644 index 7859b174da38..000000000000 --- a/src/client/pythonEnvironments/discovery/globalenv.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { logVerbose } from '../../logging'; -import { EnvironmentType } from '../info'; - -type ExecFunc = (cmd: string, args: string[]) => Promise<{ stdout: string }>; - -type TypeFinderFunc = (python: string) => Promise; -type RootFinderFunc = () => Promise; - -/** - * Build a "type finder" function that identifies pyenv environments. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param pathSep - the path separator to use (typically `path.sep`) - * @param pathJoin - typically `path.join` - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param exec - the function to use to run pyenv - */ -export function getPyenvTypeFinder( - homedir: string, - // - pathSep: string, - pathJoin: (...parts: string[]) => string, - // - getEnvVar: (name: string) => string | undefined, - exec: ExecFunc, -): TypeFinderFunc { - const find = getPyenvRootFinder(homedir, pathJoin, getEnvVar, exec); - return async (python) => { - const root = await find(); - if (root && python.startsWith(`${root}${pathSep}`)) { - return EnvironmentType.Pyenv; - } - return undefined; - }; -} - -/** - * Build a "root finder" function that finds pyenv environments. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param pathJoin - typically `path.join` - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param exec - the function to use to run pyenv - */ -export function getPyenvRootFinder( - homedir: string, - pathJoin: (...parts: string[]) => string, - getEnvVar: (name: string) => string | undefined, - exec: ExecFunc, -): RootFinderFunc { - return async () => { - const root = getEnvVar('PYENV_ROOT'); - if (root /* ...or empty... */) { - return root; - } - - try { - const result = await exec('pyenv', ['root']); - const text = result.stdout.trim(); - if (text.length > 0) { - return text; - } - } catch (err) { - // Ignore the error. - logVerbose(`"pyenv root" failed (${err})`); - } - return pathJoin(homedir, '.pyenv'); - }; -} diff --git a/src/client/pythonEnvironments/discovery/index.ts b/src/client/pythonEnvironments/discovery/index.ts deleted file mode 100644 index 512db04b3627..000000000000 --- a/src/client/pythonEnvironments/discovery/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * Decide if the given Python executable looks like the MacOS default Python. - */ -export function isMacDefaultPythonPath(pythonPath: string): boolean { - return pythonPath === 'python' || pythonPath === '/usr/bin/python'; -} diff --git a/src/client/pythonEnvironments/discovery/locators/helpers.ts b/src/client/pythonEnvironments/discovery/locators/helpers.ts deleted file mode 100644 index 7842951e9ae8..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/helpers.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as fsapi from 'fs-extra'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { traceError } from '../../../common/logger'; -import { IS_WINDOWS } from '../../../common/platform/constants'; -import { IFileSystem } from '../../../common/platform/types'; -import { IInterpreterLocatorHelper } from '../../../interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../interpreter/locators/types'; -import { EnvironmentType, PythonEnvironment } from '../../info'; - -const CheckPythonInterpreterRegEx = IS_WINDOWS ? /^python(\d+(.\d+)?)?\.exe$/ : /^python(\d+(.\d+)?)?$/; - -export async function lookForInterpretersInDirectory(pathToCheck: string): Promise { - // Technically, we should be able to use fs.getFiles(). However, - // that breaks some tests. So we stick with the broader behavior. - try { - // TODO https://github.com/microsoft/vscode-python/issues/11338 - const files = await fsapi.readdir(pathToCheck); - return files - .map((filename) => path.join(pathToCheck, filename)) - .filter((fileName) => CheckPythonInterpreterRegEx.test(path.basename(fileName))); - } catch (err) { - traceError('Python Extension (lookForInterpretersInDirectory.fs.readdir):', err); - return [] as string[]; - } -} - -@injectable() -export class InterpreterLocatorHelper implements IInterpreterLocatorHelper { - constructor( - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IPipEnvServiceHelper) private readonly pipEnvServiceHelper: IPipEnvServiceHelper, - ) {} - - public async mergeInterpreters(interpreters: PythonEnvironment[]): Promise { - const items = interpreters - .map((item) => ({ ...item })) - .map((item) => { - item.path = path.normalize(item.path); - return item; - }) - .reduce((accumulator, current: PythonEnvironment) => { - const currentVersion = current && current.version ? current.version.raw : undefined; - let existingItem = accumulator.find((item) => { - // If same version and same base path, then ignore. - // Could be Python 3.6 with path = python.exe, and Python 3.6 and path = python3.exe. - if ( - item.version && - item.version.raw === currentVersion && - item.path && - current.path && - this.fs.arePathsSame(path.dirname(item.path), path.dirname(current.path)) - ) { - return true; - } - return false; - }); - if (!existingItem) { - accumulator.push(current); - } else { - // Preserve type information. - // Possible we identified environment as unknown, but a later provider has identified env type. - if ( - existingItem.envType === EnvironmentType.Unknown && - current.envType !== EnvironmentType.Unknown - ) { - existingItem.envType = current.envType; - } - const props: (keyof PythonEnvironment)[] = [ - 'envName', - 'envPath', - 'path', - 'sysPrefix', - 'architecture', - 'sysVersion', - 'version', - ]; - props.forEach((prop) => { - if (existingItem && !existingItem[prop] && current[prop]) { - existingItem = { ...existingItem, [prop]: current[prop] }; - } - }); - } - return accumulator; - }, []); - // This stuff needs to be fast. - await Promise.all( - items.map(async (item) => { - const info = await this.pipEnvServiceHelper.getPipEnvInfo(item.path); - if (info) { - item.envType = EnvironmentType.Pipenv; - item.pipEnvWorkspaceFolder = info.workspaceFolder.fsPath; - item.envName = info.envName || item.envName; - } - }), - ); - return items; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts deleted file mode 100644 index 4038ddc39a6a..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint-disable max-classes-per-file */ - -import { inject, injectable } from 'inversify'; -import { flatten } from 'lodash'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; -import { traceDecorators } from '../../../common/logger'; -import { IPlatformService } from '../../../common/platform/types'; -import { IDisposableRegistry } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; -import { OSType } from '../../../common/utils/platform'; -import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GetInterpreterOptions, - GLOBAL_VIRTUAL_ENV_SERVICE, - IInterpreterLocatorHelper, - IInterpreterLocatorService, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvironment } from '../../info'; -import { isHiddenInterpreter } from './services/interpreterFilter'; - -/** - * Facilitates locating Python interpreters. - */ -@injectable() -export class PythonInterpreterLocatorService implements IInterpreterLocatorService { - public didTriggerInterpreterSuggestions: boolean; - - private readonly disposables: Disposable[] = []; - - private readonly platform: IPlatformService; - - private readonly interpreterLocatorHelper: IInterpreterLocatorHelper; - - private readonly _hasInterpreters: Deferred; - - private readonly onLocatingEmitter: EventEmitter> = new EventEmitter< - Promise - >(); - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this._hasInterpreters = createDeferred(); - serviceContainer.get(IDisposableRegistry).push(this); - this.platform = serviceContainer.get(IPlatformService); - this.interpreterLocatorHelper = serviceContainer.get(IInterpreterLocatorHelper); - this.didTriggerInterpreterSuggestions = false; - } - - /** - * This class should never emit events when we're locating. - * The events will be fired by the individual locators retrieved in `getLocators`. - * - * @readonly - * @type {Event>} - * @memberof PythonInterpreterLocatorService - */ - public get onLocating(): Event> { - return this.onLocatingEmitter.event; - } - - public get hasInterpreters(): Promise { - return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - public dispose(): void { - this.disposables.forEach((disposable) => disposable.dispose()); - } - - /** - * Return the list of known Python interpreters. - * - * The optional resource arg may control where locators look for - * interpreters. - */ - @traceDecorators.verbose('Get Interpreters') - public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise { - const locators = this.getLocators(options); - const promises = locators.map(async (provider) => provider.getInterpreters(resource)); - locators.forEach((locator) => { - locator.hasInterpreters - .then((found) => { - if (found) { - this._hasInterpreters.resolve(true); - } - }) - .ignoreErrors(); - }); - const listOfInterpreters = await Promise.all(promises); - - const items = flatten(listOfInterpreters) - .filter((item) => !!item) - .filter((item) => !isHiddenInterpreter(item)); - this._hasInterpreters.resolve(items.length > 0); - return this.interpreterLocatorHelper.mergeInterpreters(items); - } - - /** - * Return the list of applicable interpreter locators. - * - * The locators are pulled from the registry. - */ - private getLocators(options?: GetInterpreterOptions): IInterpreterLocatorService[] { - // The order of the services is important. - // The order is important because the data sources at the bottom of the list do not contain all, - // the information about the interpreters (e.g. type, environment name, etc). - // This way, the items returned from the top of the list will win, when we combine the items returned. - const keys: [string, OSType | undefined][] = [ - [WINDOWS_REGISTRY_SERVICE, OSType.Windows], - [CONDA_ENV_SERVICE, undefined], - [CONDA_ENV_FILE_SERVICE, undefined], - [PIPENV_SERVICE, undefined], - [GLOBAL_VIRTUAL_ENV_SERVICE, undefined], - [WORKSPACE_VIRTUAL_ENV_SERVICE, undefined], - [KNOWN_PATH_SERVICE, undefined], - [CURRENT_PATH_SERVICE, undefined], - ]; - - const locators = keys - .filter((item) => item[1] === undefined || item[1] === this.platform.osType) - .map((item) => this.serviceContainer.get(IInterpreterLocatorService, item[0])); - - // Set it to true the first time the user selects an interpreter - if (!this.didTriggerInterpreterSuggestions && options?.onSuggestion === true) { - this.didTriggerInterpreterSuggestions = true; - locators.forEach((locator) => { - locator.didTriggerInterpreterSuggestions = true; - }); - } - - return locators; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/progressService.ts b/src/client/pythonEnvironments/discovery/locators/progressService.ts deleted file mode 100644 index ce3d0fa9aaa6..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/progressService.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Disposable, Event, EventEmitter } from 'vscode'; -import { traceDecorators } from '../../../common/logger'; -import { IDisposableRegistry } from '../../../common/types'; -import { createDeferredFrom, Deferred } from '../../../common/utils/async'; -import { noop } from '../../../common/utils/misc'; -import { IInterpreterLocatorProgressService, IInterpreterLocatorService } from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvironment } from '../../info'; - -@injectable() -export class InterpreterLocatorProgressService implements IInterpreterLocatorProgressService { - private deferreds: Deferred[] = []; - - private readonly refreshing = new EventEmitter(); - - private readonly refreshed = new EventEmitter(); - - private readonly locators: IInterpreterLocatorService[] = []; - - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDisposableRegistry) private readonly disposables: Disposable[], - ) { - this.locators = serviceContainer.getAll(IInterpreterLocatorService); - } - - public get onRefreshing(): Event { - return this.refreshing.event; - } - - public get onRefreshed(): Event { - return this.refreshed.event; - } - - public async activate(): Promise { - this.locators.forEach((locator) => { - locator.onLocating(this.handleProgress, this, this.disposables); - }); - } - - @traceDecorators.verbose('Detected refreshing of Interpreters') - private handleProgress(promise: Promise) { - this.deferreds.push(createDeferredFrom(promise)); - this.notifyRefreshing(); - this.checkProgress(); - } - - @traceDecorators.verbose('All locators have completed locating') - private notifyCompleted() { - this.refreshed.fire(); - } - - @traceDecorators.verbose('Notify locators are locating') - private notifyRefreshing() { - this.refreshing.fire(); - } - - private checkProgress(): void { - if (this.deferreds.length === 0) { - return; - } - if (this.areAllItemsComplete()) { - this.notifyCompleted(); - return; - } - Promise.all(this.deferreds.map((item) => item.promise)) - .catch(noop) - .then(() => this.checkProgress()) - .ignoreErrors(); - } - - @traceDecorators.verbose('Checking whether locactors have completed locating') - private areAllItemsComplete() { - this.deferreds = this.deferreds.filter((item) => !item.completed); - return this.deferreds.length === 0; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts b/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts deleted file mode 100644 index 0fad8da4460c..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable max-classes-per-file */ - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { ICurrentProcess, IPathUtils } from '../../../../common/types'; -import { IInterpreterHelper, IKnownSearchPathsForInterpreters } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { lookForInterpretersInDirectory } from '../helpers'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -// eslint-disable-next-line global-require -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); - -/** - * Locates "known" paths. - */ -@injectable() -export class KnownPathsService extends CacheableLocatorService { - public constructor( - @inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: IKnownSearchPathsForInterpreters, - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('KnownPathsService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // eslint-disable-next-line - public dispose(): void { - // No body - } - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(): Promise { - return this.suggestionsFromKnownPaths(); - } - - /** - * Return the located interpreters. - */ - private suggestionsFromKnownPaths() { - const promises = this.knownSearchPaths.getSearchPaths().map((dir) => this.getInterpretersInDirectory(dir)); - return Promise.all(promises) - .then((listOfInterpreters) => flatten(listOfInterpreters)) - .then((interpreters) => interpreters.filter((item) => item.length > 0)) - .then((interpreters) => - Promise.all(interpreters.map((interpreter) => this.getInterpreterDetails(interpreter))), - ) - .then((interpreters) => - interpreters.filter((interpreter) => !!interpreter).map((interpreter) => interpreter!), - ); - } - - /** - * Return the information about the identified interpreter binary. - */ - private async getInterpreterDetails(interpreter: string) { - const details = await this.helper.getInterpreterInformation(interpreter); - if (!details) { - return undefined; - } - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: interpreter, - envType: EnvironmentType.Unknown, - }; - } - - /** - * Return the interpreters in the given directory. - */ - private getInterpretersInDirectory(dir: string) { - const fs = this.serviceContainer.get(IFileSystem); - return fs - .directoryExists(dir) - .then((exists) => (exists ? lookForInterpretersInDirectory(dir) : Promise.resolve([]))); - } -} - -@injectable() -export class KnownSearchPathsForInterpreters implements IKnownSearchPathsForInterpreters { - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} - - /** - * Return the paths where Python interpreters might be found. - */ - public getSearchPaths(): string[] { - const currentProcess = this.serviceContainer.get(ICurrentProcess); - const platformService = this.serviceContainer.get(IPlatformService); - const pathUtils = this.serviceContainer.get(IPathUtils); - - const searchPaths = currentProcess.env[platformService.pathVariableName]!.split(pathUtils.delimiter) - .map((p) => p.trim()) - .filter((p) => p.length > 0); - - if (!platformService.isWindows) { - ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'].forEach((p) => { - searchPaths.push(p); - searchPaths.push(path.join(pathUtils.home, p)); - }); - // Add support for paths such as /Users/xxx/anaconda/bin. - if (pathUtils.home && pathUtils.home !== '') { - searchPaths.push(path.join(pathUtils.home, 'anaconda', 'bin')); - searchPaths.push(path.join(pathUtils.home, 'python', 'bin')); - } - } - return searchPaths; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts deleted file mode 100644 index e99cbfd8b19a..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { injectable, unmanaged } from 'inversify'; -import { flatten, noop } from 'lodash'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IInterpreterHelper, IVirtualEnvironmentsSearchPathProvider } from '../../../../interpreter/contracts'; -import { IVirtualEnvironmentManager } from '../../../../interpreter/virtualEnvs/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { lookForInterpretersInDirectory } from '../helpers'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -@injectable() -export class BaseVirtualEnvService extends CacheableLocatorService { - private readonly virtualEnvMgr: IVirtualEnvironmentManager; - - private readonly helper: IInterpreterHelper; - - private readonly fileSystem: IFileSystem; - - public constructor( - @unmanaged() private searchPathsProvider: IVirtualEnvironmentsSearchPathProvider, - @unmanaged() serviceContainer: IServiceContainer, - @unmanaged() name: string, - @unmanaged() cachePerWorkspace = false, - ) { - super(name, serviceContainer, cachePerWorkspace); - this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.helper = serviceContainer.get(IInterpreterHelper); - this.fileSystem = serviceContainer.get(IFileSystem); - } - - // eslint-disable-next-line class-methods-use-this - public dispose(): void { - noop(); - } - - protected getInterpretersImplementation(resource?: Uri): Promise { - return this.suggestionsFromKnownVenvs(resource); - } - - private async suggestionsFromKnownVenvs(resource?: Uri) { - const searchPaths = await this.searchPathsProvider.getSearchPaths(resource); - return Promise.all( - searchPaths.map((dir) => this.lookForInterpretersInVenvs(dir, resource)), - ).then((listOfInterpreters) => flatten(listOfInterpreters)); - } - - private async lookForInterpretersInVenvs(pathToCheck: string, resource?: Uri) { - return this.fileSystem - .getSubDirectories(pathToCheck) - .then((subDirs) => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs))) - .then((dirs) => dirs.filter((dir) => dir.length > 0)) - .then((dirs) => Promise.all(dirs.map((d) => lookForInterpretersInDirectory(d)))) - .then((pathsWithInterpreters) => flatten(pathsWithInterpreters)) - .then((interpreters) => - Promise.all(interpreters.map((interpreter) => this.getVirtualEnvDetails(interpreter, resource))), - ) - .then((interpreters) => - interpreters.filter((interpreter) => !!interpreter).map((interpreter) => interpreter!), - ) // NOSONAR - .catch((err) => { - traceError('Python Extension (lookForInterpretersInVenvs):', err); - // Ignore exceptions. - return [] as PythonEnvironment[]; - }); - } - - private getProspectiveDirectoriesForLookup(subDirs: string[]) { - const platform = this.serviceContainer.get(IPlatformService); - const dirToLookFor = platform.virtualEnvBinName; - return subDirs.map((subDir) => - this.fileSystem - .getSubDirectories(subDir) - .then((dirs) => { - const scriptOrBinDirs = dirs.filter((dir) => { - const folderName = path.basename(dir); - return this.fileSystem.arePathsSame(folderName, dirToLookFor); - }); - return scriptOrBinDirs.length === 1 ? scriptOrBinDirs[0] : ''; - }) - .catch((err) => { - traceError('Python Extension (getProspectiveDirectoriesForLookup):', err); - // Ignore exceptions. - return ''; - }), - ); - } - - private async getVirtualEnvDetails(interpreter: string, resource?: Uri): Promise { - return Promise.all([ - this.helper.getInterpreterInformation(interpreter), - this.virtualEnvMgr.getEnvironmentName(interpreter, resource), - this.virtualEnvMgr.getEnvironmentType(interpreter, resource), - ]).then( - ([details, virtualEnvName, type]): Promise => { - if (!details) { - return Promise.resolve(undefined); - } - this._hasInterpreters.resolve(true); - return Promise.resolve({ - ...(details as PythonEnvironment), - envName: virtualEnvName, - type: type! as EnvironmentType, - }); - }, - ); - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts b/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts deleted file mode 100644 index 3d26b2650128..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// eslint-disable-next-line max-classes-per-file -import { injectable, unmanaged } from 'inversify'; -import * as md5 from 'md5'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import '../../../../common/extensions'; -import { traceDecorators, traceVerbose } from '../../../../common/logger'; -import { IDisposableRegistry, IPersistentState, IPersistentStateFactory } from '../../../../common/types'; -import { createDeferred, Deferred } from '../../../../common/utils/async'; -import { StopWatch } from '../../../../common/utils/stopWatch'; -import { - GetInterpreterOptions, - IInterpreterLocatorService, - IInterpreterWatcher, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { PythonEnvironment } from '../../../info'; - -/** - * This class exists so that the interpreter fetching can be cached in between tests. Normally - * this cache resides in memory for the duration of the CacheableLocatorService's lifetime, but in the case - * of our functional tests, we want the cached data to exist outside of each test (where each test will destroy the CacheableLocatorService) - * This gives each test a 20 second speedup. - */ -export class CacheableLocatorPromiseCache { - private static useStatic = false; - - private static staticMap = new Map>(); - - private normalMap = new Map>(); - - public static forceUseStatic(): void { - CacheableLocatorPromiseCache.useStatic = true; - } - - public static forceUseNormal(): void { - CacheableLocatorPromiseCache.useStatic = false; - } - - public get(key: string): Deferred | undefined { - if (CacheableLocatorPromiseCache.useStatic) { - return CacheableLocatorPromiseCache.staticMap.get(key); - } - return this.normalMap.get(key); - } - - public set(key: string, value: Deferred): void { - if (CacheableLocatorPromiseCache.useStatic) { - CacheableLocatorPromiseCache.staticMap.set(key, value); - } else { - this.normalMap.set(key, value); - } - } - - public delete(key: string): void { - if (CacheableLocatorPromiseCache.useStatic) { - CacheableLocatorPromiseCache.staticMap.delete(key); - } else { - this.normalMap.delete(key); - } - } -} - -@injectable() -export abstract class CacheableLocatorService implements IInterpreterLocatorService { - protected readonly _hasInterpreters: Deferred; - - private readonly promisesPerResource = new CacheableLocatorPromiseCache(); - - private readonly handlersAddedToResource = new Set(); - - private readonly cacheKeyPrefix: string; - - private readonly locating = new EventEmitter>(); - - private _didTriggerInterpreterSuggestions: boolean; - - constructor( - @unmanaged() private readonly name: string, - @unmanaged() protected readonly serviceContainer: IServiceContainer, - @unmanaged() private cachePerWorkspace: boolean = false, - ) { - this._hasInterpreters = createDeferred(); - this.cacheKeyPrefix = `INTERPRETERS_CACHE_v3_${name}`; - this._didTriggerInterpreterSuggestions = false; - } - - public get didTriggerInterpreterSuggestions(): boolean { - return this._didTriggerInterpreterSuggestions; - } - - public set didTriggerInterpreterSuggestions(value: boolean) { - this._didTriggerInterpreterSuggestions = value; - } - - public get onLocating(): Event> { - return this.locating.event; - } - - public get hasInterpreters(): Promise { - return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); - } - - public abstract dispose(): void; - - @traceDecorators.verbose('Get Interpreters in CacheableLocatorService') - public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise { - const cacheKey = this.getCacheKey(resource); - let deferred = this.promisesPerResource.get(cacheKey); - if (!deferred || options?.ignoreCache) { - deferred = createDeferred(); - this.promisesPerResource.set(cacheKey, deferred); - - this.addHandlersForInterpreterWatchers(cacheKey, resource).ignoreErrors(); - - const stopWatch = new StopWatch(); - this.getInterpretersImplementation(resource) - .then(async (items) => { - await this.cacheInterpreters(items, resource); - traceVerbose( - `Interpreters returned by ${this.name} are of count ${Array.isArray(items) ? items.length : 0}`, - ); - traceVerbose(`Interpreters returned by ${this.name} are ${JSON.stringify(items)}`); - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { - interpreters: Array.isArray(items) ? items.length : 0, - }); - deferred!.resolve(items); - }) - .catch((ex) => { - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, {}, ex); - deferred!.reject(ex); - }); - - this.locating.fire(deferred.promise); - } - deferred.promise - .then((items) => this._hasInterpreters.resolve(items.length > 0)) - .catch(() => this._hasInterpreters.resolve(false)); - - if (deferred.completed) { - return deferred.promise; - } - - const cachedInterpreters = options?.ignoreCache ? undefined : this.getCachedInterpreters(resource); - return Array.isArray(cachedInterpreters) ? cachedInterpreters : deferred.promise; - } - - protected async addHandlersForInterpreterWatchers(cacheKey: string, resource: Uri | undefined): Promise { - if (this.handlersAddedToResource.has(cacheKey)) { - return; - } - this.handlersAddedToResource.add(cacheKey); - const watchers = await this.getInterpreterWatchers(resource); - const disposableRegisry = this.serviceContainer.get(IDisposableRegistry); - watchers.forEach((watcher) => { - watcher.onDidCreate( - () => { - traceVerbose(`Interpreter Watcher change handler for ${this.cacheKeyPrefix}`); - this.promisesPerResource.delete(cacheKey); - this.getInterpreters(resource).ignoreErrors(); - }, - this, - disposableRegisry, - ); - }); - } - - // eslint-disable-next-line class-methods-use-this - protected async getInterpreterWatchers(_resource: Uri | undefined): Promise { - return []; - } - - protected abstract getInterpretersImplementation(resource?: Uri): Promise; - - protected createPersistenceStore(resource?: Uri): IPersistentState { - const cacheKey = this.getCacheKey(resource); - const persistentFactory = this.serviceContainer.get(IPersistentStateFactory); - if (this.cachePerWorkspace) { - return persistentFactory.createWorkspacePersistentState(cacheKey, undefined); - } - return persistentFactory.createGlobalPersistentState(cacheKey, undefined); - } - - protected getCachedInterpreters(resource?: Uri): PythonEnvironment[] | undefined { - const persistence = this.createPersistenceStore(resource); - if (!Array.isArray(persistence.value)) { - return undefined; - } - return persistence.value.map((item) => ({ - ...item, - cachedEntry: true, - })); - } - - protected async cacheInterpreters(interpreters: PythonEnvironment[], resource?: Uri): Promise { - const persistence = this.createPersistenceStore(resource); - await persistence.updateValue(interpreters); - } - - protected getCacheKey(resource?: Uri): string { - if (!resource || !this.cachePerWorkspace) { - return this.cacheKeyPrefix; - } - // Ensure we have separate caches per workspace where necessary.ÃŽ - const workspaceService = this.serviceContainer.get(IWorkspaceService); - if (!Array.isArray(workspaceService.workspaceFolders)) { - return this.cacheKeyPrefix; - } - - const workspace = workspaceService.getWorkspaceFolder(resource); - return workspace ? `${this.cacheKeyPrefix}:${md5(workspace.uri.fsPath)}` : this.cacheKeyPrefix; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts deleted file mode 100644 index 83825d58af71..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * This file is essential to have for us to discover conda interpreters, `CondaEnvService` alone is not sufficient. - * CondaEnvService runs ` env list` command, which requires that we know the path to conda. - * In cases where we're not able to figure that out, we can still use this file to discover paths to conda environments. - * More details: https://github.com/microsoft/vscode-python/issues/8886 - */ - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem } from '../../../../common/platform/types'; -import { ICondaLocatorService, IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { AnacondaCompanyName } from '../../../common/environmentManagers/conda'; - -/** - * Locate conda env interpreters based on the "conda environments file". - */ -@injectable() -export class CondaEnvFileService extends CacheableLocatorService { - constructor( - @inject(IInterpreterHelper) private helperService: IInterpreterHelper, - @inject(ICondaLocatorService) private condaService: ICondaLocatorService, - @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('CondaEnvFileService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - - public dispose(): void { - // No body - } - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(_resource?: Uri): Promise { - return this.getSuggestionsFromConda(); - } - - /** - * Return the list of interpreters identified by the "conda environments file". - */ - private async getSuggestionsFromConda(): Promise { - if (!this.condaService.condaEnvironmentsFile) { - return []; - } - return this.fileSystem - .fileExists(this.condaService.condaEnvironmentsFile!) - .then((exists) => - exists ? this.getEnvironmentsFromFile(this.condaService.condaEnvironmentsFile!) : Promise.resolve([]), - ); - } - - /** - * Return the list of environments identified in the given file. - */ - private async getEnvironmentsFromFile(envFile: string) { - try { - const fileContents = await this.fileSystem.readFile(envFile); - const environmentPaths = fileContents - .split(/\r?\n/g) - .map((environmentPath) => environmentPath.trim()) - .filter((environmentPath) => environmentPath.length > 0); - - const interpreters = ( - await Promise.all( - environmentPaths.map((environmentPath) => this.getInterpreterDetails(environmentPath)), - ) - ) - .filter((item) => !!item) - .map((item) => item!); - - const environments = await this.condaService.getCondaEnvironments(true); - if (Array.isArray(environments) && environments.length > 0) { - interpreters.forEach((interpreter) => { - const environment = environments.find((item) => - this.fileSystem.arePathsSame(item.path, interpreter!.envPath!), - ); - if (environment) { - interpreter.envName = environment!.name; - } - }); - } - return interpreters; - } catch (err) { - traceError('Python Extension (getEnvironmentsFromFile.readFile):', err); - // Ignore errors in reading the file. - return [] as PythonEnvironment[]; - } - } - - /** - * Return the interpreter info for the given anaconda environment. - */ - private async getInterpreterDetails(environmentPath: string): Promise { - const interpreter = this.condaService.getInterpreterPath(environmentPath); - if (!interpreter || !(await this.fileSystem.fileExists(interpreter))) { - return undefined; - } - - const details = await this.helperService.getInterpreterInformation(interpreter); - if (!details) { - return undefined; - } - const envName = details.envName ? details.envName : path.basename(environmentPath); - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: interpreter, - companyDisplayName: AnacondaCompanyName, - envType: EnvironmentType.Conda, - envPath: environmentPath, - envName, - }; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts deleted file mode 100644 index d9de871996c2..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem } from '../../../../common/platform/types'; -import { ICondaLocatorService, IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { parseCondaInfo } from '../../../common/environmentManagers/conda'; - -/** - * Locates conda env interpreters based on the conda service's info. - */ -@injectable() -export class CondaEnvService extends CacheableLocatorService { - constructor( - @inject(ICondaLocatorService) private condaService: ICondaLocatorService, - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IFileSystem) private fileSystem: IFileSystem, - ) { - super('CondaEnvService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - - public dispose(): void { - // No body - } - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(_resource?: Uri): Promise { - return this.getSuggestionsFromConda(); - } - - /** - * Return the list of interpreters for all the conda envs. - */ - private async getSuggestionsFromConda(): Promise { - try { - const info = await this.condaService.getCondaInfo(); - if (!info) { - return []; - } - const interpreters = await parseCondaInfo( - info, - (env) => this.condaService.getInterpreterPath(env), - (f) => this.fileSystem.fileExists(f), - (p) => this.helper.getInterpreterInformation(p), - ); - this._hasInterpreters.resolve(interpreters.length > 0); - const environments = await this.condaService.getCondaEnvironments(true); - if (Array.isArray(environments) && environments.length > 0) { - interpreters.forEach((interpreter) => { - const environment = environments.find((item) => - this.fileSystem.arePathsSame(item.path, interpreter!.envPath!), - ); - if (environment) { - interpreter.envName = environment!.name; - } - }); - } - - return interpreters; - } catch (ex) { - // Failed because either: - // 1. conda is not installed. - // 2. `conda info --json` has changed signature. - // 3. output of `conda info --json` has changed in structure. - // In all cases, we can't offer conda pythonPath suggestions. - traceError('Failed to get Suggestions from conda', ex); - return []; - } - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts b/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts deleted file mode 100644 index 87c9a878630c..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import '../../../../common/extensions'; - -/** - * Helpers for conda. - */ - -/** - * Parses output returned by the command `conda env list`. - * Sample output is as follows: - * # conda environments: - * # - * base * /Users/donjayamanne/anaconda3 - * one /Users/donjayamanne/anaconda3/envs/one - * py27 /Users/donjayamanne/anaconda3/envs/py27 - * py36 /Users/donjayamanne/anaconda3/envs/py36 - * three /Users/donjayamanne/anaconda3/envs/three - * /Users/donjayamanne/anaconda3/envs/four - * /Users/donjayamanne/anaconda3/envs/five 5 - * aaaa_bbbb_cccc_dddd_eeee_ffff_gggg /Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg - * with*star /Users/donjayamanne/anaconda3/envs/with*star - * "/Users/donjayamanne/anaconda3/envs/seven " - */ -export function parseCondaEnvFileContents( - condaEnvFileContents: string, -): { name: string; path: string; isActive: boolean }[] | undefined { - // Don't trim the lines. `path` portion of the line can end with a space. - const lines = condaEnvFileContents.splitLines({ trim: false }); - const envs: { name: string; path: string; isActive: boolean }[] = []; - - lines.forEach((line) => { - const item = parseCondaEnvFileLine(line); - if (item) { - envs.push(item); - } - }); - - return envs.length > 0 ? envs : undefined; -} - -function parseCondaEnvFileLine(line: string): { name: string; path: string; isActive: boolean } | undefined { - // Empty lines or lines starting with `#` are comments and can be ignored. - if (line.length === 0 || line.startsWith('#')) { - return undefined; - } - - // This extraction is based on the following code for `conda env list`: - // https://github.com/conda/conda/blob/f207a2114c388fd17644ee3a5f980aa7cf86b04b/conda/cli/common.py#L188 - // It uses "%-20s %s %s" as the format string. Where the middle %s is '*' - // if the environment is active, and ' ' if it is not active. - - // If conda environment was created using `-p` then it may NOT have a name. - // Use empty string as default name for envs created using path only. - let name = ''; - let remainder = line; - - // The `name` and `path` parts are separated by at least 5 spaces. We cannot - // use a single space here since it can be part of the name (see below for - // name spec). Another assumption here is that `name` does not start with - // 5*spaces or somewhere in the center. However, ` name` or `a b` is - // a valid name when using --clone. Highly unlikely that users will have this - // form as the environment name. lastIndexOf() can also be used but that assumes - // that `path` does NOT end with 5*spaces. - let spaceIndex = line.indexOf(' '); - if (spaceIndex === -1) { - // This means the environment name is longer than 17 characters and it is - // active. Try ' * ' for separator between name and path. - spaceIndex = line.indexOf(' * '); - } - - if (spaceIndex > 0) { - // Parsing `name` - // > `conda create -n ` - // conda environment `name` should NOT have following characters - // ('/', ' ', ':', '#'). So we can use the index of 5*space - // characters to extract the name. - // - // > `conda create --clone one -p "~/envs/one two"` - // this can generate a cloned env with name `one two`. This is - // only allowed for cloned environments. In both cases, the best - // separator is 5*spaces. It is highly unlikely that users will have - // 5*spaces in their environment name. - // - // Notes: When using clone if the path has a trailing space, it will - // not be preserved for the name. Trailing spaces in environment names - // are NOT allowed. But leading spaces are allowed. Currently there no - // special separator character between name and path, other than spaces. - // We will need a well known separator if this ever becomes a issue. - name = line.substring(0, spaceIndex).trimRight(); - remainder = line.substring(spaceIndex); - } - - // Detecting Active Environment: - // Only active environment will have `*` between `name` and `path`. `name` - // or `path` can have `*` in them as well. So we have to look for `*` in - // between `name` and `path`. We already extracted the name, the next non- - // whitespace character should either be `*` or environment path. - remainder = remainder.trimLeft(); - const isActive = remainder.startsWith('*'); - - // Parsing `path` - // If `*` is the first then we can skip that character. Trim left again, - // don't do trim() or trimRight(), since paths can end with a space. - remainder = (isActive ? remainder.substring(1) : remainder).trimLeft(); - - return { name, path: remainder, isActive }; -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaLocatorService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaLocatorService.ts deleted file mode 100644 index d96117ea3717..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaLocatorService.ts +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ConfigurationChangeEvent, Uri } from 'vscode'; - -import { IWorkspaceService } from '../../../../common/application/types'; -import { traceDecorators, traceError, traceVerbose, traceWarning } from '../../../../common/logger'; -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 { - ICondaLocatorService, - IInterpreterLocatorService, - WINDOWS_REGISTRY_SERVICE, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { compareSemVerLikeVersions } from '../../../base/info/pythonVersion'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CondaEnvironmentInfo, CondaInfo } from '../../../common/environmentManagers/conda'; -import { parseCondaEnvFileContents } from './condaHelper'; - -const untildify: (value: string) => string = require('untildify'); - -// This glob pattern will match all of the following: -// ~/anaconda/bin/conda, ~/anaconda3/bin/conda, ~/miniconda/bin/conda, ~/miniconda3/bin/conda -// /usr/share/anaconda/bin/conda, /usr/share/anaconda3/bin/conda, /usr/share/miniconda/bin/conda, -// /usr/share/miniconda3/bin/conda - -const condaGlobPathsForLinuxMac = [ - untildify('~/opt/*conda*/bin/conda'), - '/opt/*conda*/bin/conda', - '/usr/share/*conda*/bin/conda', - untildify('~/*conda*/bin/conda'), -]; - -const CondaLocationsGlob = `{${condaGlobPathsForLinuxMac.join(',')}}`; - -// ...and for windows, the known default install locations: -const condaGlobPathsForWindows = [ - '/ProgramData/[Mm]iniconda*/Scripts/conda.exe', - '/ProgramData/[Aa]naconda*/Scripts/conda.exe', - untildify('~/[Mm]iniconda*/Scripts/conda.exe'), - untildify('~/[Aa]naconda*/Scripts/conda.exe'), - untildify('~/AppData/Local/Continuum/[Mm]iniconda*/Scripts/conda.exe'), - untildify('~/AppData/Local/Continuum/[Aa]naconda*/Scripts/conda.exe'), -]; - -// format for glob processing: -const CondaLocationsGlobWin = `{${condaGlobPathsForWindows.join(',')}}`; - -/** - * A wrapper around a conda installation. - */ -@injectable() -export class CondaLocatorService implements ICondaLocatorService { - public get condaEnvironmentsFile(): string | undefined { - const homeDir = this.platform.isWindows ? process.env.USERPROFILE : process.env.HOME || process.env.HOMEPATH; - return homeDir ? path.join(homeDir, '.conda', 'environments.txt') : undefined; - } - - private condaFile?: Promise; - - constructor( - @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, - @inject(IPlatformService) private platform: IPlatformService, - @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IPersistentStateFactory) private persistentStateFactory: IPersistentStateFactory, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - ) { - this.addCondaPathChangedHandler(); - } - - /** - * Return the highest Python version from the given list. - */ - private static getLatestVersion(interpreters: PythonEnvironment[]): PythonEnvironment | undefined { - const sortedInterpreters = interpreters.slice(); - - sortedInterpreters.sort((a, b) => - a.version && b.version ? compareSemVerLikeVersions(a.version, b.version) : 0, - ); - if (sortedInterpreters.length > 0) { - return sortedInterpreters[sortedInterpreters.length - 1]; - } - - return undefined; - } - - /** - * Is the given interpreter from conda? - */ - private static detectCondaEnvironment(env: PythonEnvironment): boolean { - return ( - env.envType === EnvironmentType.Conda || - (env.displayName ? env.displayName : '').toUpperCase().includes('ANACONDA') || - (env.companyDisplayName ? env.companyDisplayName : '').toUpperCase().includes('ANACONDA') || - (env.companyDisplayName ? env.companyDisplayName : '').toUpperCase().includes('CONTINUUM') - ); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - - public dispose(): void {} // eslint-disable-line - - /** - * Return the path to the "conda file". - */ - public async getCondaFile(): Promise { - if (!this.condaFile) { - this.condaFile = this.getCondaFileImpl(); - } - return (await this.condaFile)!; - } - - /** - * Can the shell find conda (to run it)? - */ - public async isCondaInCurrentPath(): Promise { - const processService = await this.processServiceFactory.create(); - return processService - .exec('conda', ['--version']) - .then((output) => output.stdout.length > 0) - .catch(() => false); - } - - /** - * Return the info reported by the conda install. - * The result is cached for 30s. - */ - @cache(60_000) - public async getCondaInfo(): Promise { - try { - const condaFile = await this.getCondaFile(); - const processService = await this.processServiceFactory.create(); - const condaInfo = await processService.exec(condaFile, ['info', '--json']).then((output) => output.stdout); - - return JSON.parse(condaInfo) as CondaInfo; - } catch (ex) { - // Failed because either: - // 1. conda is not installed. - // 2. `conda info --json` has changed signature. - return undefined; - } - } - - /** - * Determines whether a python interpreter is a conda environment or not. - * The check is done by simply looking for the 'conda-meta' directory. - * @param {string} interpreterPath - * @returns {Promise} - * @memberof CondaLocatorService - */ - public async isCondaEnvironment(interpreterPath: string): Promise { - const dir = path.dirname(interpreterPath); - const { isWindows } = this.platform; - const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta'); - return this.fileSystem.directoryExists(condaMetaDirectory); - } - - /** - * Return (env name, interpreter filename) for the interpreter. - */ - public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> { - const isCondaEnv = await this.isCondaEnvironment(interpreterPath); - if (!isCondaEnv) { - return undefined; - } - let environments = await this.getCondaEnvironments(false); - const dir = path.dirname(interpreterPath); - - // If interpreter is in bin or Scripts, then go up one level - const subDirName = path.basename(dir); - const goUpOnLevel = ['BIN', 'SCRIPTS'].indexOf(subDirName.toUpperCase()) !== -1; - const interpreterPathToMatch = goUpOnLevel ? path.join(dir, '..') : dir; - - // From the list of conda environments find this dir. - let matchingEnvs = Array.isArray(environments) - ? environments.filter((item) => this.fileSystem.arePathsSame(item.path, interpreterPathToMatch)) - : []; - if (matchingEnvs.length === 0) { - environments = await this.getCondaEnvironments(true); - matchingEnvs = Array.isArray(environments) - ? environments.filter((item) => this.fileSystem.arePathsSame(item.path, interpreterPathToMatch)) - : []; - } - - if (matchingEnvs.length > 0) { - return { name: matchingEnvs[0].name, path: interpreterPathToMatch }; - } - - // If still not available, then the user created the env after starting vs code. - // The only solution is to get the user to re-start vscode. - return undefined; - } - - /** - * Return the list of conda envs (by name, interpreter filename). - */ - @traceDecorators.verbose('Get Conda environments') - public async getCondaEnvironments(ignoreCache: boolean): Promise { - // Global cache. - const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ - data: CondaEnvironmentInfo[] | undefined; - }>('CONDA_ENVIRONMENTS', undefined); - if (!ignoreCache && globalPersistence.value) { - return globalPersistence.value.data; - } - - try { - const condaFile = await this.getCondaFile(); - const processService = await this.processServiceFactory.create(); - let envInfo = await processService.exec(condaFile, ['env', 'list']).then((output) => output.stdout); - traceVerbose(`Conda Env List ${envInfo}}`); - if (!envInfo) { - traceVerbose('Conda env list failure, attempting path additions.'); - // Try adding different folders to the path. Miniconda fails to run - // without them. - const baseFolder = path.dirname(path.dirname(condaFile)); - const binFolder = path.join(baseFolder, 'bin'); - const condaBinFolder = path.join(baseFolder, 'condabin'); - const libaryBinFolder = path.join(baseFolder, 'library', 'bin'); - const newEnv = process.env; - newEnv.PATH = `${binFolder};${condaBinFolder};${libaryBinFolder};${newEnv.PATH}`; - traceVerbose(`Attempting new path for conda env list: ${newEnv.PATH}`); - envInfo = await processService - .exec(condaFile, ['env', 'list'], { env: newEnv }) - .then((output) => output.stdout); - } - const environments = parseCondaEnvFileContents(envInfo); - await globalPersistence.updateValue({ data: environments }); - return environments; - } catch (ex) { - await globalPersistence.updateValue({ data: undefined }); - // Failed because either: - // 1. conda is not installed. - // 2. `conda env list has changed signature. - traceError('Failed to get conda environment list from conda', ex); - return undefined; - } - } - - /** - * Return the interpreter's filename for the given environment. - */ - public getInterpreterPath(condaEnvironmentPath: string): string { - // where to find the Python binary within a conda env. - const relativePath = this.platform.isWindows ? 'python.exe' : path.join('bin', 'python'); - return path.join(condaEnvironmentPath, relativePath); - } - - /** - * Get the conda exe from the path to an interpreter's python. This might be different than the - * globally registered conda.exe. - * - * The value is cached for a while. - * The only way this can change is if user installs conda into this same environment. - * Generally we expect that to happen the other way, the user creates a conda environment with conda in it. - */ - @traceDecorators.verbose('Get Conda File from interpreter') - @cache(120_000) - public async _getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise { - const condaExe = this.platform.isWindows ? 'conda.exe' : 'conda'; - const scriptsDir = this.platform.isWindows ? 'Scripts' : 'bin'; - const interpreterDir = interpreterPath ? path.dirname(interpreterPath) : ''; - - // Might be in a situation where this is not the default python env, but rather one running - // from a virtualenv - const envsPos = envName ? interpreterDir.indexOf(path.join('envs', envName)) : -1; - if (envsPos > 0) { - // This should be where the original python was run from when the environment was created. - const originalPath = interpreterDir.slice(0, envsPos); - let condaPath1 = path.join(originalPath, condaExe); - - if (await this.fileSystem.fileExists(condaPath1)) { - return condaPath1; - } - - // Also look in the scripts directory here too. - condaPath1 = path.join(originalPath, scriptsDir, condaExe); - if (await this.fileSystem.fileExists(condaPath1)) { - return condaPath1; - } - } - - let condaPath2 = path.join(interpreterDir, condaExe); - if (await this.fileSystem.fileExists(condaPath2)) { - return condaPath2; - } - // Conda path has changed locations, check the new location in the scripts directory after checking - // the old location - condaPath2 = path.join(interpreterDir, scriptsDir, condaExe); - if (await this.fileSystem.fileExists(condaPath2)) { - return condaPath2; - } - - return undefined; - } - - private addCondaPathChangedHandler() { - const disposable = this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); - this.disposableRegistry.push(disposable); - } - - private async onDidChangeConfiguration(event: ConfigurationChangeEvent) { - const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders!.map((workspace) => workspace.uri) - : [undefined]; - if (workspacesUris.findIndex((uri) => event.affectsConfiguration('python.condaPath', uri)) === -1) { - return; - } - this.condaFile = undefined; - } - - /** - * Return the path to the "conda file", if there is one (in known locations). - */ - private async getCondaFileImpl() { - const settings = this.configService.getSettings(); - - const setting = settings.condaPath; - if (setting && setting !== '') { - return setting; - } - - const isAvailable = await this.isCondaInCurrentPath(); - if (isAvailable) { - return 'conda'; - } - if (this.platform.isWindows) { - const interpreters: PythonEnvironment[] = await this.getWinRegEnvs(); - const condaInterpreters = interpreters.filter(CondaLocatorService.detectCondaEnvironment); - const condaInterpreter = CondaLocatorService.getLatestVersion(condaInterpreters); - if (condaInterpreter) { - const interpreterPath = await this._getCondaFileFromInterpreter( - condaInterpreter.path, - condaInterpreter.envName, - ); - if (interpreterPath) { - return interpreterPath; - } - } - } - return this.getCondaFileFromKnownLocations(); - } - - private async getWinRegEnvs(): Promise { - const registryLookupForConda: IInterpreterLocatorService = this.serviceContainer.get< - IInterpreterLocatorService - >(IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE); - return registryLookupForConda.getInterpreters(); - } - - /** - * Return the path to the "conda file", if there is one (in known locations). - * Note: For now we simply return the first one found. - */ - private async getCondaFileFromKnownLocations(): Promise { - const globPattern = this.platform.isWindows ? CondaLocationsGlobWin : CondaLocationsGlob; - const condaFiles = await this.fileSystem.search(globPattern).catch((failReason) => { - traceWarning( - 'Default conda location search failed.', - `Searching for default install locations for conda results in error: ${failReason}`, - ); - return []; - }); - const validCondaFiles = condaFiles.filter((condaPath) => condaPath.length > 0); - return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0]; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts b/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts deleted file mode 100644 index 90a3d4867538..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable max-classes-per-file */ - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { traceError, traceInfo } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import * as internalPython from '../../../../common/process/internal/python'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService } from '../../../../common/types'; -import { OSType } from '../../../../common/utils/platform'; -import { IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IPythonInPathCommandProvider } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -/** - * Locates the currently configured Python interpreter. - * - * If no interpreter is configured then it falls back to the system - * Python (3 then 2). - */ -@injectable() -export class CurrentPathService extends CacheableLocatorService { - private readonly fs: IFileSystem; - - public constructor( - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @inject(IPythonInPathCommandProvider) private readonly pythonCommandProvider: IPythonInPathCommandProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('CurrentPathService', serviceContainer); - this.fs = serviceContainer.get(IFileSystem); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - - public dispose(): void { - // No body - } - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(resource?: Uri): Promise { - return this.suggestionsFromKnownPaths(resource); - } - - /** - * Return the located interpreters. - */ - private async suggestionsFromKnownPaths(resource?: Uri) { - const configSettings = this.serviceContainer - .get(IConfigurationService) - .getSettings(resource); - const pathsToCheck = [...this.pythonCommandProvider.getCommands(), { command: configSettings.pythonPath }]; - - const pythonPaths = Promise.all(pathsToCheck.map((item) => this.getInterpreter(item))); - return pythonPaths - .then((interpreters) => interpreters.filter((item) => item.length > 0)) - - .then((interpreters) => - Promise.all(interpreters.map((interpreter) => this.getInterpreterDetails(interpreter))), - ) - .then((interpreters) => interpreters.filter((item) => !!item).map((item) => item!)); - } - - /** - * Return the information about the identified interpreter binary. - */ - private async getInterpreterDetails(pythonPath: string): Promise { - return this.helper.getInterpreterInformation(pythonPath).then((details) => { - if (!details) { - return undefined; - } - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: pythonPath, - envType: details.envType ? details.envType : EnvironmentType.Unknown, - }; - }); - } - - /** - * Return the path to the interpreter (or the default if not found). - */ - private async getInterpreter(options: { command: string; args?: string[] }) { - try { - const processService = await this.processServiceFactory.create(); - const pyArgs = Array.isArray(options.args) ? options.args : []; - const [args, parse] = internalPython.getExecutable(); - return processService - .exec(options.command, pyArgs.concat(args), {}) - .then((output) => parse(output.stdout)) - .then(async (value) => { - if (value.length > 0 && (await this.fs.fileExists(value))) { - return value; - } - traceError( - `Detection of Python Interpreter for Command ${options.command} and args ${pyArgs.join( - ' ', - )} failed as file ${value} does not exist`, - ); - return ''; - }) - .catch(() => { - traceInfo( - `Detection of Python Interpreter for Command ${options.command} and args ${pyArgs.join( - ' ', - )} failed`, - ); - return ''; - }); // Ignore exceptions in getting the executable. - } catch (ex) { - traceError(`Detection of Python Interpreter for Command ${options.command} failed`, ex); - return ''; // Ignore exceptions in getting the executable. - } - } -} - -@injectable() -export class PythonInPathCommandProvider implements IPythonInPathCommandProvider { - constructor(@inject(IPlatformService) private readonly platform: IPlatformService) {} - - public getCommands(): { command: string; args?: string[] }[] { - const paths = ['python3.7', 'python3.6', 'python3', 'python2', 'python'].map((item) => ({ command: item })); - if (this.platform.osType !== OSType.Windows) { - return paths; - } - - const versions = ['3.7', '3.6', '3', '2']; - return paths.concat(versions.map((version) => ({ command: 'py', args: [`-${version}`] }))); - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts deleted file mode 100644 index 32090f772f86..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable max-classes-per-file */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IConfigurationService, ICurrentProcess } from '../../../../common/types'; -import { IVirtualEnvironmentsSearchPathProvider } from '../../../../interpreter/contracts'; -import { IVirtualEnvironmentManager } from '../../../../interpreter/virtualEnvs/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { BaseVirtualEnvService } from './baseVirtualEnvService'; - -const untildify: (value: string) => string = require('untildify'); - -@injectable() -export class GlobalVirtualEnvService extends BaseVirtualEnvService { - public constructor( - @inject(IVirtualEnvironmentsSearchPathProvider) - @named('global') - globalVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super(globalVirtualEnvPathProvider, serviceContainer, 'VirtualEnvService'); - } -} - -@injectable() -export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider { - private readonly config: IConfigurationService; - - private readonly currentProcess: ICurrentProcess; - - private readonly virtualEnvMgr: IVirtualEnvironmentManager; - - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.config = serviceContainer.get(IConfigurationService); - this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.currentProcess = serviceContainer.get(ICurrentProcess); - } - - public async getSearchPaths(resource?: Uri): Promise { - const homedir = os.homedir(); - const venvFolders = [ - 'envs', - '.pyenv', - '.direnv', - '.virtualenvs', - ...this.config.getSettings(resource).venvFolders, - ]; - const folders = [...new Set(venvFolders.map((item) => path.join(homedir, item)))]; - - // Add support for the WORKON_HOME environment variable used by pipenv and virtualenvwrapper. - const workonHomePath = this.currentProcess.env.WORKON_HOME; - if (workonHomePath) { - folders.push(untildify(workonHomePath)); - } - - const pyenvRoot = await this.virtualEnvMgr.getPyEnvRoot(resource); - if (pyenvRoot) { - folders.push(pyenvRoot); - folders.push(path.join(pyenvRoot, 'versions')); - } - return folders; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts b/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts deleted file mode 100644 index ac49d8edb23e..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as fsapi from 'fs-extra'; -import * as path from 'path'; -import { traceVerbose } from '../../../../common/logger'; -import { getHashString } from '../../../../common/platform/fileSystem'; - -export async function getInterpreterHash(pythonPath: string): Promise { - let data = ''; - try { - const stat = await fsapi.lstat(pythonPath); - data = `${stat.ctime.valueOf()}-${stat.mtime.valueOf()}`; - } catch (err) { - if (err.code === 'UNKNOWN') { - // This is probably due to the following bug in node file system: - // https://github.com/nodejs/node/issues/33024 - // https://github.com/nodejs/node/issues/36790 - // The work around does not work in all cases, especially for the - // Windows Store python. - - try { - // A soft alternative is to check the mtime of the parent directory and use the - // path and mtime as hash. We don't want to run Python to determine this. - const stat = await fsapi.lstat(path.dirname(pythonPath)); - data = `${stat.ctime.valueOf()}-${stat.mtime.valueOf()}-${pythonPath}`; - } catch (err2) { - traceVerbose('Error when computing file hash using parent directory: ', err2); - throw err2; - } - } else { - traceVerbose('Error when computing file hash: ', err); - } - } - return getHashString(data); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts b/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts deleted file mode 100644 index 84097aff3c44..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { PythonEnvironment } from '../../../info'; -import { isRestrictedWindowsStoreInterpreterPath } from './windowsStoreInterpreter'; - -export function isHiddenInterpreter(interpreter: PythonEnvironment): boolean { - // Any set of rules to hide interpreters should go here - return isRestrictedWindowsStoreInterpreterPath(interpreter.path); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts b/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts deleted file mode 100644 index aa9756f26ee0..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { traceDecorators } from '../../../../common/logger'; -import { createDeferred } from '../../../../common/utils/async'; -import { - IInterpreterWatcher, - IInterpreterWatcherBuilder, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { WorkspaceVirtualEnvWatcherService } from './workspaceVirtualEnvWatcherService'; - -@injectable() -export class InterpreterWatcherBuilder implements IInterpreterWatcherBuilder { - private readonly watchersByResource = new Map>(); - - /** - * Creates an instance of InterpreterWatcherBuilder. - * Inject the DI container, as we need to get a new instance of IInterpreterWatcher to build it. - * @param {IWorkspaceService} workspaceService - * @param {IServiceContainer} serviceContainer - * @memberof InterpreterWatcherBuilder - */ - constructor( - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - ) {} - - @traceDecorators.verbose('Build the workspace interpreter watcher') - public async getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise { - const key = this.getResourceKey(resource); - if (!this.watchersByResource.has(key)) { - const deferred = createDeferred(); - this.watchersByResource.set(key, deferred.promise); - const watcher = this.serviceContainer.get( - IInterpreterWatcher, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - await watcher.register(resource); - deferred.resolve(watcher); - } - return this.watchersByResource.get(key)!; - } - - protected getResourceKey(resource: Uri | undefined): string { - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - return workspaceFolder ? workspaceFolder.uri.fsPath : ''; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts deleted file mode 100644 index 55b8ebc9a49c..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../../../common/application/types'; -import { traceError, traceWarning } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService, ICurrentProcess } from '../../../../common/types'; -import { StopWatch } from '../../../../common/utils/stopWatch'; -import { GetInterpreterOptions, IInterpreterHelper, IPipEnvService } from '../../../../interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -const pipEnvFileNameVariable = 'PIPENV_PIPFILE'; - -@injectable() -export class PipEnvService extends CacheableLocatorService implements IPipEnvService { - private readonly helper: IInterpreterHelper; - - private readonly processServiceFactory: IProcessServiceFactory; - - private readonly workspace: IWorkspaceService; - - private readonly fs: IFileSystem; - - private readonly configService: IConfigurationService; - - private readonly pipEnvServiceHelper: IPipEnvServiceHelper; - - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super('PipEnvService', serviceContainer, true); - this.helper = this.serviceContainer.get(IInterpreterHelper); - this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); - this.workspace = this.serviceContainer.get(IWorkspaceService); - this.fs = this.serviceContainer.get(IFileSystem); - this.configService = this.serviceContainer.get(IConfigurationService); - this.pipEnvServiceHelper = this.serviceContainer.get(IPipEnvServiceHelper); - } - - public dispose(): void { - // No body - } - - public async isRelatedPipEnvironment(dir: string, pythonPath: string): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return false; - } - - // In PipEnv, the name of the cwd is used as a prefix in the virtual env. - if (pythonPath.indexOf(`${path.sep}${path.basename(dir)}-`) === -1) { - return false; - } - const envName = await this.getInterpreterPathFromPipenv(dir, true); - return !!envName; - } - - public get executable(): string { - return this.didTriggerInterpreterSuggestions ? this.configService.getSettings().pipenvPath : ''; - } - - public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return []; - } - - const stopwatch = new StopWatch(); - const startDiscoveryTime = stopwatch.elapsedTime; - - const interpreters = await super.getInterpreters(resource, options); - - const discoveryDuration = stopwatch.elapsedTime - startDiscoveryTime; - sendTelemetryEvent(EventName.PIPENV_INTERPRETER_DISCOVERY, discoveryDuration); - - return interpreters; - } - - protected getInterpretersImplementation(resource?: Uri): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return Promise.resolve([]); - } - - const pipenvCwd = this.getPipenvWorkingDirectory(resource); - if (!pipenvCwd) { - return Promise.resolve([]); - } - - return this.getInterpreterFromPipenv(pipenvCwd) - .then((item) => (item ? [item] : [])) - .catch(() => []); - } - - private async getInterpreterFromPipenv(pipenvCwd: string): Promise { - const interpreterPath = await this.getInterpreterPathFromPipenv(pipenvCwd); - if (!interpreterPath) { - return undefined; - } - - const details = await this.helper.getInterpreterInformation(interpreterPath); - if (!details) { - return undefined; - } - this._hasInterpreters.resolve(true); - await this.pipEnvServiceHelper.trackWorkspaceFolder(interpreterPath, Uri.file(pipenvCwd)); - return { - ...(details as PythonEnvironment), - path: interpreterPath, - envType: EnvironmentType.Pipenv, - pipEnvWorkspaceFolder: pipenvCwd, - }; - } - - private getPipenvWorkingDirectory(resource?: Uri): string | undefined { - // The file is not in a workspace. However, workspace may be opened - // and file is just a random file opened from elsewhere. In this case - // we still want to provide interpreter associated with the workspace. - // Otherwise if user tries and formats the file, we may end up using - // plain pip module installer to bring in the formatter and it is wrong. - const wsFolder = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; - return wsFolder ? wsFolder.uri.fsPath : this.workspace.rootPath; - } - - private async getInterpreterPathFromPipenv(cwd: string, ignoreErrors = false): Promise { - // Quick check before actually running pipenv - if (!(await this.checkIfPipFileExists(cwd))) { - return undefined; - } - try { - // call pipenv --version just to see if pipenv is in the PATH - const version = await this.invokePipenv('--version', cwd); - if (version === undefined) { - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage( - `Workspace contains Pipfile but '${this.executable}' was not found. Make sure '${this.executable}' is on the PATH.`, - ); - return undefined; - } - // The --py command will fail if the virtual environment has not been setup yet. - // so call pipenv --venv to check for the virtual environment first. - const venv = await this.invokePipenv('--venv', cwd); - if (venv === undefined) { - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage( - 'Workspace contains Pipfile but the associated virtual environment has not been setup. Setup the virtual environment manually if needed.', - ); - return undefined; - } - const pythonPath = await this.invokePipenv('--py', cwd); - return pythonPath && (await this.fs.fileExists(pythonPath)) ? pythonPath : undefined; - } catch (error) { - traceError('PipEnv identification failed', error); - if (ignoreErrors) { - return undefined; - } - } - - return undefined; - } - - private async checkIfPipFileExists(cwd: string): Promise { - const currentProcess = this.serviceContainer.get(ICurrentProcess); - const pipFileName = currentProcess.env[pipEnvFileNameVariable]; - if (typeof pipFileName === 'string' && (await this.fs.fileExists(path.join(cwd, pipFileName)))) { - return true; - } - if (await this.fs.fileExists(path.join(cwd, 'Pipfile'))) { - return true; - } - return false; - } - - private async invokePipenv(arg: string, rootPath: string): Promise { - try { - const processService = await this.processServiceFactory.create(Uri.file(rootPath)); - const execName = this.executable; - const result = await processService.exec(execName, [arg], { cwd: rootPath }); - if (result) { - const stdout = result.stdout ? result.stdout.trim() : ''; - const stderr = result.stderr ? result.stderr.trim() : ''; - if (stderr.length > 0 && stdout.length === 0) { - throw new Error(stderr); - } - return stdout; - } - } catch (error) { - const platformService = this.serviceContainer.get(IPlatformService); - const currentProc = this.serviceContainer.get(ICurrentProcess); - const enviromentVariableValues: Record = { - LC_ALL: currentProc.env.LC_ALL, - LANG: currentProc.env.LANG, - }; - enviromentVariableValues[platformService.pathVariableName] = - currentProc.env[platformService.pathVariableName]; - - traceWarning('Error in invoking PipEnv', error); - traceWarning(`Relevant Environment Variables ${JSON.stringify(enviromentVariableValues, undefined, 4)}`); - } - - return undefined; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts b/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts deleted file mode 100644 index 91b67a5a4534..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPersistentState, IPersistentStateFactory } from '../../../../common/types'; -import { IPipEnvServiceHelper } from '../../../../interpreter/locators/types'; - -type PipEnvInformation = { pythonPath: string; workspaceFolder: string; envName: string }; -@injectable() -export class PipEnvServiceHelper implements IPipEnvServiceHelper { - private initialized = false; - - private readonly state: IPersistentState>; - - constructor( - @inject(IPersistentStateFactory) private readonly statefactory: IPersistentStateFactory, - @inject(IFileSystem) private readonly fs: IFileSystem, - ) { - this.state = this.statefactory.createGlobalPersistentState>( - 'PipEnvInformation', - [], - ); - } - - public async getPipEnvInfo(pythonPath: string): Promise<{ workspaceFolder: Uri; envName: string } | undefined> { - await this.initializeStateStore(); - const info = this.state.value.find((item) => this.fs.arePathsSame(item.pythonPath, pythonPath)); - return info ? { workspaceFolder: Uri.file(info.workspaceFolder), envName: info.envName } : undefined; - } - - public async trackWorkspaceFolder(pythonPath: string, workspaceFolder: Uri): Promise { - await this.initializeStateStore(); - const values = [...this.state.value].filter((item) => !this.fs.arePathsSame(item.pythonPath, pythonPath)); - const envName = path.basename(workspaceFolder.fsPath); - values.push({ pythonPath, workspaceFolder: workspaceFolder.fsPath, envName }); - await this.state.updateValue(values); - } - - protected async initializeStateStore(): Promise { - if (this.initialized) { - return; - } - const list = await Promise.all( - this.state.value.map(async (item) => ((await this.fs.fileExists(item.pythonPath)) ? item : undefined)), - ); - const filteredList = list.filter((item) => !!item) as PipEnvInformation[]; - await this.state.updateValue(filteredList); - this.initialized = true; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts deleted file mode 100644 index 8f9c622a4fae..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem, IPlatformService, IRegistry, RegistryHive } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; -import { Architecture } from '../../../../common/utils/platform'; -import { IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { parsePythonVersion } from '../../../info/pythonVersion'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { AnacondaCompanyName } from '../../../common/environmentManagers/conda'; -import { isWindowsStoreInterpreter, isRestrictedWindowsStoreInterpreterPath } from './windowsStoreInterpreter'; - -const flatten = require('lodash/flatten'); - -const DefaultPythonExecutable = 'python.exe'; - -const CompaniesToIgnore = ['PYLAUNCHER']; - -const PythonCoreCompanyDisplayName = 'Python Software Foundation'; - -const PythonCoreComany = 'PYTHONCORE'; - -const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.']; - -type CompanyInterpreter = { - companyKey: string; - hive: RegistryHive; - arch?: Architecture; -}; - -@injectable() -export class WindowsRegistryService extends CacheableLocatorService { - private readonly pathUtils: IPathUtils; - - private readonly fs: IFileSystem; - - constructor( - @inject(IRegistry) private registry: IRegistry, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('WindowsRegistryService', serviceContainer); - this.pathUtils = serviceContainer.get(IPathUtils); - this.fs = serviceContainer.get(IFileSystem); - } - - public dispose(): void { - // Do nothing. - } - - protected async getInterpretersImplementation(): Promise { - return this.platform.isWindows - ? this.getInterpretersFromRegistry().catch((ex) => { - traceError('Fetching interpreters from registry failed with error', ex); - return []; - }) - : []; - } - - private async getInterpretersFromRegistry(): Promise { - // https://github.com/python/peps/blob/master/pep-0514.txt#L357 - const hkcuArch = this.platform.is64bit ? undefined : Architecture.x86; - const promises: Promise[] = [ - this.getCompanies(RegistryHive.HKCU, hkcuArch), - this.getCompanies(RegistryHive.HKLM, Architecture.x86), - ]; - // https://github.com/Microsoft/PTVS/blob/ebfc4ca8bab234d453f15ee426af3b208f3c143c/Python/Product/Cookiecutter/Shared/Interpreters/PythonRegistrySearch.cs#L44 - if (this.platform.is64bit) { - promises.push(this.getCompanies(RegistryHive.HKLM, Architecture.x64)); - } - - const companies = await Promise.all(promises); - const companyInterpreters = await Promise.all( - flatten(companies) - .filter((item: CompanyInterpreter) => item !== undefined && item !== null) - .map((company: CompanyInterpreter) => - this.getInterpretersForCompany(company.companyKey, company.hive, company.arch), - ), - ); - - return flatten(companyInterpreters) - .filter((item: PythonEnvironment | null | undefined) => item !== undefined && item !== null) - .reduce((prev: PythonEnvironment[], current: PythonEnvironment) => { - if (prev.findIndex((item) => item.path.toUpperCase() === current.path.toUpperCase()) === -1) { - prev.push(current); - } - return prev; - }, []); - } - - private async getCompanies(hive: RegistryHive, arch?: Architecture): Promise { - return this.registry - .getKeys('\\Software\\Python', hive, arch) - .then((companyKeys) => - companyKeys - .filter( - (companyKey) => - CompaniesToIgnore.indexOf(this.pathUtils.basename(companyKey).toUpperCase()) === -1, - ) - .map((companyKey) => ({ companyKey, hive, arch })), - ); - } - - private async getInterpretersForCompany(companyKey: string, hive: RegistryHive, arch?: Architecture) { - const tagKeys = await this.registry.getKeys(companyKey, hive, arch); - return Promise.all( - tagKeys.map((tagKey) => this.getInreterpreterDetailsForCompany(tagKey, companyKey, hive, arch)), - ); - } - - private getInreterpreterDetailsForCompany( - tagKey: string, - companyKey: string, - hive: RegistryHive, - arch?: Architecture, - ): Promise { - const key = `${tagKey}\\InstallPath`; - type InterpreterInformation = - | null - | undefined - | { - installPath: string; - executablePath?: string; - displayName?: string; - version?: string; - companyDisplayName?: string; - }; - return this.registry - .getValue(key, hive, arch) - .then((installPath) => { - // Install path is mandatory. - if (!installPath) { - return Promise.resolve(null); - } - // Check if 'ExecutablePath' exists. - // Remember Python 2.7 doesn't have 'ExecutablePath' (there could be others). - // Treat all other values as optional. - return Promise.all([ - Promise.resolve(installPath), - this.registry.getValue(key, hive, arch, 'ExecutablePath'), - this.registry.getValue(tagKey, hive, arch, 'SysVersion'), - this.getCompanyDisplayName(companyKey, hive, arch), - ]).then(([installedPath, executablePath, version, companyDisplayName]) => { - companyDisplayName = - AnacondaCompanyNames.indexOf(companyDisplayName!) === -1 - ? companyDisplayName - : AnacondaCompanyName; - - return { - installPath: installedPath, - executablePath, - version, - companyDisplayName, - } as InterpreterInformation; - }); - }) - .then(async (interpreterInfo?: InterpreterInformation) => { - if (!interpreterInfo) { - return undefined; - } - - const executablePath = - interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 - ? interpreterInfo.executablePath - : path.join(interpreterInfo.installPath, DefaultPythonExecutable); - - if (isRestrictedWindowsStoreInterpreterPath(executablePath)) { - return undefined; - } - const helper = this.serviceContainer.get(IInterpreterHelper); - const details = await helper.getInterpreterInformation(executablePath); - if (!details) { - return undefined; - } - const version = interpreterInfo.version - ? this.pathUtils.basename(interpreterInfo.version) - : this.pathUtils.basename(tagKey); - this._hasInterpreters.resolve(true); - - return { - ...(details as PythonEnvironment), - path: executablePath, - // Do not use version info from registry, this doesn't contain the release level. - // Give preference to what we have retrieved from getInterpreterInformation. - version: details.version || parsePythonVersion(version), - companyDisplayName: interpreterInfo.companyDisplayName, - envType: (await isWindowsStoreInterpreter(executablePath)) - ? EnvironmentType.WindowsStore - : EnvironmentType.Unknown, - } as PythonEnvironment; - }) - .then((interpreter) => - interpreter - ? this.fs - .fileExists(interpreter.path) - .catch(() => false) - .then((exists) => (exists ? interpreter : null)) - : null, - ) - .catch((error) => { - traceError( - `Failed to retrieve interpreter details for company ${companyKey},tag: ${tagKey}, hive: ${hive}, arch: ${arch}`, - error, - ); - return null; - }); - } - - private async getCompanyDisplayName(companyKey: string, hive: RegistryHive, arch?: Architecture) { - const displayName = await this.registry.getValue(companyKey, hive, arch, 'DisplayName'); - if (displayName && displayName.length > 0) { - return displayName; - } - const company = this.pathUtils.basename(companyKey); - return company.toUpperCase() === PythonCoreComany ? PythonCoreCompanyDisplayName : company; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts deleted file mode 100644 index 57ebff9c920a..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -/** - * When using Windows Store interpreter the path that should be used is under - * %USERPROFILE%\AppData\Local\Microsoft\WindowsApps\python*.exe. The python.exe path - * under ProgramFiles\WindowsApps should not be used at all. Execute permissions on - * that instance of the store interpreter are restricted to system. Paths under - * %USERPROFILE%\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation* are also ok - * to use. But currently this results in duplicate entries. - * Interpreters that fall into this category will not be displayed to the users. - * - * @param {string} pythonPath - * @returns {boolean} - */ -export function isRestrictedWindowsStoreInterpreterPath(pythonPath: string): boolean { - const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\'); - - return ( - pythonPathToCompare.includes('\\Program Files\\WindowsApps\\'.toUpperCase()) || - pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\PythonSoftwareFoundation'.toUpperCase()) - ); -} - -/** - * Whether this is a Windows Store/App Interpreter. - * - * @param {string} pythonPath - * @param {IComponentAdapter} pyenvs - * @returns {boolean} - * @memberof WindowsStoreInterpreter - */ -export async function isWindowsStoreInterpreter(pythonPath: string): Promise { - const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\'); - return ( - pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\'.toUpperCase()) || - pythonPathToCompare.includes('\\Program Files\\WindowsApps\\'.toUpperCase()) || - pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\PythonSoftwareFoundation'.toUpperCase()) - ); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts deleted file mode 100644 index 8eaee21bc70a..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable max-classes-per-file */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IConfigurationService } from '../../../../common/types'; -import { - IInterpreterWatcher, - IInterpreterWatcherBuilder, - IVirtualEnvironmentsSearchPathProvider, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { BaseVirtualEnvService } from './baseVirtualEnvService'; - -const untildify = require('untildify'); - -@injectable() -export class WorkspaceVirtualEnvService extends BaseVirtualEnvService { - public constructor( - @inject(IVirtualEnvironmentsSearchPathProvider) - @named('workspace') - workspaceVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IInterpreterWatcherBuilder) private readonly builder: IInterpreterWatcherBuilder, - ) { - super(workspaceVirtualEnvPathProvider, serviceContainer, 'WorkspaceVirtualEnvService', true); - } - - protected async getInterpreterWatchers(resource: Uri | undefined): Promise { - return [await this.builder.getWorkspaceVirtualEnvInterpreterWatcher(resource)]; - } -} - -@injectable() -export class WorkspaceVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider { - public constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - - public async getSearchPaths(resource?: Uri): Promise { - const configService = this.serviceContainer.get(IConfigurationService); - const paths: string[] = []; - const { venvPath } = configService.getSettings(resource); - if (venvPath) { - paths.push(untildify(venvPath)); - } - const workspaceService = this.serviceContainer.get(IWorkspaceService); - if (Array.isArray(workspaceService.workspaceFolders) && workspaceService.workspaceFolders.length > 0) { - let wsPath: string | undefined; - if (resource && workspaceService.workspaceFolders.length > 1) { - const wkspaceFolder = workspaceService.getWorkspaceFolder(resource); - if (wkspaceFolder) { - wsPath = wkspaceFolder.uri.fsPath; - } - } else { - wsPath = workspaceService.workspaceFolders[0].uri.fsPath; - } - if (wsPath) { - paths.push(wsPath); - paths.push(path.join(wsPath, '.direnv')); - } - } - return paths; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts b/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts deleted file mode 100644 index ee66e34aec2c..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { clearTimeout, setTimeout } from 'timers'; -import { Disposable, Event, EventEmitter, FileSystemWatcher, RelativePattern, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import '../../../../common/extensions'; -import { traceDecorators, traceVerbose } from '../../../../common/logger'; -import { IPlatformService } from '../../../../common/platform/types'; -import { IPythonExecutionFactory } from '../../../../common/process/types'; -import { IDisposableRegistry, Resource } from '../../../../common/types'; -import { IInterpreterWatcher } from '../../../../interpreter/contracts'; - -const maxTimeToWaitForEnvCreation = 60_000; -const timeToPollForEnvCreation = 2_000; - -@injectable() -export class WorkspaceVirtualEnvWatcherService implements IInterpreterWatcher, Disposable { - private readonly didCreate: EventEmitter; - - private timers = new Map(); - - private fsWatchers: FileSystemWatcher[] = []; - - private resource: Resource; - - constructor( - @inject(IDisposableRegistry) private readonly disposableRegistry: Disposable[], - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IPlatformService) private readonly platformService: IPlatformService, - @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory, - ) { - this.didCreate = new EventEmitter(); - disposableRegistry.push(this); - } - - public get onDidCreate(): Event { - return this.didCreate.event; - } - - public dispose(): void { - this.clearTimers(); - } - - @traceDecorators.verbose('Register Interpreter Watcher') - public async register(resource: Resource): Promise { - if (this.fsWatchers.length > 0) { - return; - } - this.resource = resource; - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - const executable = this.platformService.isWindows ? 'python.exe' : 'python'; - const patterns = [path.join('*', executable), path.join('*', '*', executable)]; - - for (const pattern of patterns) { - const globPatern = workspaceFolder ? new RelativePattern(workspaceFolder.uri.fsPath, pattern) : pattern; - traceVerbose(`Create file systemwatcher with pattern ${pattern}`); - - const fsWatcher = this.workspaceService.createFileSystemWatcher(globPatern); - fsWatcher.onDidCreate((e) => this.createHandler(e), this, this.disposableRegistry); - - this.disposableRegistry.push(fsWatcher); - this.fsWatchers.push(fsWatcher); - } - } - - @traceDecorators.verbose('Interpreter Watcher change handler') - public async createHandler(e: Uri): Promise { - this.didCreate.fire(this.resource); - // On Windows, creation of environments are very slow, hence lets notify again after - // the python executable is accessible (i.e. when we can launch the process). - this.notifyCreationWhenReady(e.fsPath).ignoreErrors(); - } - - protected async notifyCreationWhenReady(pythonPath: string): Promise { - const counter = (this.timers.get(pythonPath)?.counter ?? -1) + 1; - const isValid = await this.isValidExecutable(pythonPath); - if (isValid) { - if (counter > 0) { - this.didCreate.fire(this.resource); - } - this.timers.delete(pythonPath); - return; - } - if (counter > maxTimeToWaitForEnvCreation / timeToPollForEnvCreation) { - // Send notification before we give up trying. - this.didCreate.fire(this.resource); - this.timers.delete(pythonPath); - return; - } - - const timer = setTimeout( - () => this.notifyCreationWhenReady(pythonPath).ignoreErrors(), - timeToPollForEnvCreation, - ); - this.timers.set(pythonPath, { timer, counter }); - } - - private clearTimers() { - this.timers.forEach((item) => clearTimeout(item.timer)); - this.timers.clear(); - } - - private async isValidExecutable(pythonPath: string): Promise { - const execService = await this.pythonExecFactory.create({ pythonPath }); - const info = await execService.getInterpreterInformation().catch(() => undefined); - return info !== undefined; - } -} diff --git a/src/client/pythonEnvironments/discovery/subenv.ts b/src/client/pythonEnvironments/discovery/subenv.ts deleted file mode 100644 index 10633706f24a..000000000000 --- a/src/client/pythonEnvironments/discovery/subenv.ts +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { EnvironmentType } from '../info'; -import { getPyenvTypeFinder } from './globalenv'; - -type ExecFunc = (cmd: string, args: string[]) => Promise<{ stdout: string }>; - -type NameFinderFunc = (python: string) => Promise; -type TypeFinderFunc = (python: string) => Promise; -type ExecutableFinderFunc = (python: string) => Promise; - -/** - * Determine the environment name for the given Python executable. - * - * @param python - the executable to inspect - * @param finders - the functions specific to different Python environment types - */ -export async function getName(python: string, finders: NameFinderFunc[]): Promise { - for (const find of finders) { - const found = await find(python); - if (found && found !== '') { - return found; - } - } - return undefined; -} - -/** - * Determine the environment type for the given Python executable. - * - * @param python - the executable to inspect - * @param finders - the functions specific to different Python environment types - */ -export async function getType(python: string, finders: TypeFinderFunc[]): Promise { - for (const find of finders) { - const found = await find(python); - if (found && found !== EnvironmentType.Unknown) { - return found; - } - } - return undefined; -} - -// ======= default sets ======== - -/** - * Build the list of default "name finder" functions to pass to `getName()`. - * - * @param dirname - the "root" of a directory tree to search - * @param pathDirname - typically `path.dirname` - * @param pathBasename - typically `path.basename` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - */ -export function getNameFinders( - dirname: string | undefined, - // - pathDirname: (filename: string) => string, - pathBasename: (filename: string) => string, - // - isPipenvRoot: (dir: string, python: string) => Promise, -): NameFinderFunc[] { - return [ - // Note that currently there is only one finder function in - // the list. That is only a temporary situation as we - // consolidate code under the py-envs component. - async (python) => { - if (dirname && (await isPipenvRoot(dirname, python))) { - // In pipenv, return the folder name of the root dir. - return pathBasename(dirname); - } - return pathBasename(pathDirname(pathDirname(python))); - }, - ]; -} - -/** - * Build the list of default "type finder" functions to pass to `getType()`. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param scripts - the names of possible activation scripts (e.g. `activate.sh`) - * @param pathSep - the path separator to use (typically `path.sep`) - * @param pathJoin - typically `path.join` - * @param pathDirname - typically `path.dirname` - * @param getCurDir - a function that returns `$CWD` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param fileExists - typically `fs.exists` - * @param exec - the function to use to run a command in a subprocess - */ -export function getTypeFinders( - homedir: string, - scripts: string[], - // - pathSep: string, - pathJoin: (...parts: string[]) => string, - pathDirname: (filename: string) => string, - // - getCurDir: () => Promise, - isPipenvRoot: (dir: string, python: string) => Promise, - getEnvVar: (name: string) => string | undefined, - fileExists: (n: string) => Promise, - exec: ExecFunc, -): TypeFinderFunc[] { - return [ - getVenvTypeFinder(pathDirname, pathJoin, fileExists), - // For now we treat pyenv as a "virtual" environment (to keep compatibility)... - getPyenvTypeFinder(homedir, pathSep, pathJoin, getEnvVar, exec), - getPipenvTypeFinder(getCurDir, isPipenvRoot), - getVirtualenvTypeFinder(scripts, pathDirname, pathJoin, fileExists), - // Lets not try to determine whether this is a conda environment or not. - ]; -} - -// ======= venv ======== - -/** - * Build a "type finder" function that identifies venv environments. - * - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVenvTypeFinder( - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): TypeFinderFunc { - return async (python: string) => { - const dir = pathDirname(python); - const VENVFILES = ['pyvenv.cfg', pathJoin('..', 'pyvenv.cfg')]; - const cfgFiles = VENVFILES.map((file) => pathJoin(dir, file)); - for (const file of cfgFiles) { - if (await fileExists(file)) { - return EnvironmentType.Venv; - } - } - return undefined; - }; -} - -/** - * Build an "executable finder" function that identifies venv environments. - * - * @param basename - the venv name or names to look for - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVenvExecutableFinder( - basename: string | string[], - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): ExecutableFinderFunc { - const basenames = typeof basename === 'string' ? [basename] : basename; - return async (python: string) => { - // Generated scripts are found in the same directory as the interpreter. - const binDir = pathDirname(python); - for (const name of basenames) { - const filename = pathJoin(binDir, name); - if (await fileExists(filename)) { - return filename; - } - } - // No matches so return undefined. - return undefined; - }; -} - -// ======= virtualenv ======== - -/** - * Build a "type finder" function that identifies virtualenv environments. - * - * @param scripts - the names of possible activation scripts (e.g. `activate.sh`) - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVirtualenvTypeFinder( - scripts: string[], - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): TypeFinderFunc { - const find = getVenvExecutableFinder(scripts, pathDirname, pathJoin, fileExists); - return async (python: string) => { - const found = await find(python); - return found !== undefined ? EnvironmentType.VirtualEnv : undefined; - }; -} - -// ======= pipenv ======== - -/** - * Build a "type finder" function that identifies pipenv environments. - * - * @param getCurDir - a function that returns `$CWD` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - */ -export function getPipenvTypeFinder( - getCurDir: () => Promise, - isPipenvRoot: (dir: string, python: string) => Promise, -): TypeFinderFunc { - return async (python: string) => { - const curDir = await getCurDir(); - if (curDir && (await isPipenvRoot(curDir, python))) { - return EnvironmentType.Pipenv; - } - return undefined; - }; -} diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 1b4aff77812b..dd3765604e38 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -22,7 +22,7 @@ import { PyenvLocator } from './base/locators/lowLevel/pyenvLocator'; import { WindowsRegistryLocator } from './base/locators/lowLevel/windowsRegistryLocator'; import { WindowsStoreLocator } from './base/locators/lowLevel/windowsStoreLocator'; import { getEnvironmentInfoService } from './base/info/environmentInfoService'; -import { isComponentEnabled, registerLegacyDiscoveryForIOC, registerNewDiscoveryForIOC } from './legacyIOC'; +import { registerNewDiscoveryForIOC } from './legacyIOC'; import { PoetryLocator } from './base/locators/lowLevel/poetryLocator'; import { createPythonEnvironments } from './api'; import { @@ -45,8 +45,6 @@ export async function initialize(ext: ExtensionState): Promise { ext.legacyIOC.serviceManager, api, ); - // Deal with legacy IOC. - await registerLegacyDiscoveryForIOC(ext.legacyIOC.serviceManager); return api; } @@ -55,12 +53,6 @@ export async function initialize(ext: ExtensionState): Promise { * Make use of the component (e.g. register with VS Code). */ export async function activate(api: IDiscoveryAPI, _ext: ExtensionState): Promise { - if (!(await isComponentEnabled())) { - return { - fullyReady: Promise.resolve(), - }; - } - /** * Force an initial background refresh of the environments. * diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index e0d028a95758..6cf280fdd8b8 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -4,72 +4,25 @@ import { injectable } from 'inversify'; import { intersection } from 'lodash'; import * as vscode from 'vscode'; -import { DiscoveryVariants } from '../common/experiments/groups'; -import { traceError, traceVerbose } from '../common/logger'; +import { traceVerbose } from '../common/logger'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { Resource } from '../common/types'; -import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GLOBAL_VIRTUAL_ENV_SERVICE, - IComponentAdapter, - ICondaService, - ICondaLocatorService, - IInterpreterLocatorHelper, - IInterpreterLocatorProgressService, - IInterpreterLocatorService, - IInterpreterWatcher, - IInterpreterWatcherBuilder, - IKnownSearchPathsForInterpreters, - INTERPRETER_LOCATOR_SERVICE, - IVirtualEnvironmentsSearchPathProvider, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, - PythonEnvironmentsChangedEvent, -} from '../interpreter/contracts'; -import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../interpreter/locators/types'; -import { VirtualEnvironmentManager } from '../interpreter/virtualEnvs'; -import { IVirtualEnvironmentManager } from '../interpreter/virtualEnvs/types'; +import { IComponentAdapter, ICondaService, PythonEnvironmentsChangedEvent } from '../interpreter/contracts'; import { IServiceManager } from '../ioc/types'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './base/info'; import { IDiscoveryAPI, PythonLocatorQuery } from './base/locator'; import { isMacDefaultPythonPath } from './base/locators/lowLevel/macDefaultLocator'; -import { inExperiment, isParentPath } from './common/externalDependencies'; -import { PythonInterpreterLocatorService } from './discovery/locators'; -import { InterpreterLocatorHelper } from './discovery/locators/helpers'; -import { InterpreterLocatorProgressService } from './discovery/locators/progressService'; -import { CondaEnvironmentInfo, isCondaEnvironment } from './common/environmentManagers/conda'; -import { CondaEnvFileService } from './discovery/locators/services/condaEnvFileService'; -import { CondaEnvService } from './discovery/locators/services/condaEnvService'; -import { CondaService } from './discovery/locators/services/condaService'; -import { CondaLocatorService } from './discovery/locators/services/condaLocatorService'; -import { CurrentPathService, PythonInPathCommandProvider } from './discovery/locators/services/currentPathService'; -import { - GlobalVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvService, -} from './discovery/locators/services/globalVirtualEnvService'; -import { InterpreterWatcherBuilder } from './discovery/locators/services/interpreterWatcherBuilder'; -import { KnownPathsService, KnownSearchPathsForInterpreters } from './discovery/locators/services/KnownPathsService'; -import { PipEnvService } from './discovery/locators/services/pipEnvService'; -import { PipEnvServiceHelper } from './discovery/locators/services/pipEnvServiceHelper'; -import { WindowsRegistryService } from './discovery/locators/services/windowsRegistryService'; -import { isWindowsStoreEnvironment } from './common/environmentManagers/windowsStoreEnv'; -import { - WorkspaceVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvService, -} from './discovery/locators/services/workspaceVirtualEnvService'; -import { WorkspaceVirtualEnvWatcherService } from './discovery/locators/services/workspaceVirtualEnvWatcherService'; +import { isParentPath } from './common/externalDependencies'; import { EnvironmentType, PythonEnvironment } from './info'; import { toSemverLikeVersion } from './base/info/pythonVersion'; import { PythonVersion } from './info/pythonVersion'; -import { IExtensionSingleActivationService } from '../activation/types'; import { EnvironmentInfoServiceQueuePriority, getEnvironmentInfoService } from './base/info/environmentInfoService'; import { createDeferred } from '../common/utils/async'; import { PythonEnvCollectionChangedEvent } from './base/watcher'; import { asyncFilter } from '../common/utils/arrayUtils'; +import { CondaEnvironmentInfo, isCondaEnvironment } from './common/environmentManagers/conda'; +import { isWindowsStoreEnvironment } from './common/environmentManagers/windowsStoreEnv'; +import { CondaService } from './common/environmentManagers/condaService'; const convertedKinds = new Map( Object.entries({ @@ -126,14 +79,6 @@ function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { return env; } - -export async function isComponentEnabled(): Promise { - const results = await Promise.all([ - inExperiment(DiscoveryVariants.discoverWithFileWatching), - inExperiment(DiscoveryVariants.discoveryWithoutFileWatching), - ]); - return results.includes(true); -} @injectable() class ComponentAdapter implements IComponentAdapter { private readonly refreshing = new vscode.EventEmitter(); @@ -357,98 +302,7 @@ class ComponentAdapter implements IComponentAdapter { } } -export async function registerLegacyDiscoveryForIOC(serviceManager: IServiceManager): Promise { - const inExp = await isComponentEnabled().catch((ex) => { - // This is mainly to support old tests, where IExperimentService was registered - // out of sequence / or not registered, so this throws an error. But we do not - // care about that error as we don't care about IExperimentService in old tests. - // But if this fails in other cases, it's a major error. Hence log it anyways. - traceError('Failed to not register old code when in Discovery experiment', ex); - return false; - }); - if (!inExp) { - serviceManager.addSingleton(IInterpreterLocatorHelper, InterpreterLocatorHelper); - serviceManager.addSingleton( - IInterpreterLocatorService, - PythonInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvFileService, - CONDA_ENV_FILE_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvService, - CONDA_ENV_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - GlobalVirtualEnvService, - GLOBAL_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvironmentsSearchPathProvider, - 'global', - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - KnownPathsService, - KNOWN_PATH_SERVICE, - ); - serviceManager.addSingleton( - IKnownSearchPathsForInterpreters, - KnownSearchPathsForInterpreters, - ); - serviceManager.addSingleton( - IInterpreterLocatorProgressService, - InterpreterLocatorProgressService, - ); - serviceManager.addBinding(IInterpreterLocatorProgressService, IExtensionSingleActivationService); - serviceManager.addSingleton( - IInterpreterLocatorService, - WorkspaceVirtualEnvService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvironmentsSearchPathProvider, - 'workspace', - ); - serviceManager.addSingleton(IInterpreterWatcherBuilder, InterpreterWatcherBuilder); - serviceManager.add( - IInterpreterWatcher, - WorkspaceVirtualEnvWatcherService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CurrentPathService, - CURRENT_PATH_SERVICE, - ); - serviceManager.addSingleton( - IPythonInPathCommandProvider, - PythonInPathCommandProvider, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - WindowsRegistryService, - WINDOWS_REGISTRY_SERVICE, - ); - serviceManager.addSingleton(IVirtualEnvironmentManager, VirtualEnvironmentManager); - serviceManager.addSingleton( - IInterpreterLocatorService, - PipEnvService, - PIPENV_SERVICE, - ); - serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper); - serviceManager.addSingleton(ICondaLocatorService, CondaLocatorService); - } - serviceManager.addSingleton(ICondaService, CondaService); -} - export function registerNewDiscoveryForIOC(serviceManager: IServiceManager, api: IDiscoveryAPI): void { + serviceManager.addSingleton(ICondaService, CondaService); serviceManager.addSingletonInstance(IComponentAdapter, new ComponentAdapter(api)); } diff --git a/src/test/common/experiments/service.unit.test.ts b/src/test/common/experiments/service.unit.test.ts index e8c8defd4fd8..c416d6cd8b1e 100644 --- a/src/test/common/experiments/service.unit.test.ts +++ b/src/test/common/experiments/service.unit.test.ts @@ -12,7 +12,6 @@ import { ApplicationEnvironment } from '../../../client/common/application/appli import { IApplicationEnvironment, IWorkspaceService } from '../../../client/common/application/types'; import { WorkspaceService } from '../../../client/common/application/workspace'; import { Channel } from '../../../client/common/constants'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { ExperimentService } from '../../../client/common/experiments/service'; import { Experiments } from '../../../client/common/utils/localize'; import * as Telemetry from '../../../client/telemetry'; @@ -183,20 +182,6 @@ suite('Experimentation service', () => { sinon.restore(); }); - test('Enable discovery experiment without file watching for all users', async () => { - configureSettings(true, [], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = experimentService.inExperimentSync(DiscoveryVariants.discoveryWithoutFileWatching); - - assert.isTrue(result); - }); - test('If the opt-in and opt-out arrays are empty, return the value from the experimentation framework for a given experiment', async () => { configureSettings(true, [], []); diff --git a/src/test/common/installer/condaInstaller.unit.test.ts b/src/test/common/installer/condaInstaller.unit.test.ts index 6da63804ea2d..c523156e5ab7 100644 --- a/src/test/common/installer/condaInstaller.unit.test.ts +++ b/src/test/common/installer/condaInstaller.unit.test.ts @@ -8,20 +8,14 @@ import { instance, mock, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; import { InterpreterUri } from '../../../client/common/installer/types'; -import { - ExecutionInfo, - IConfigurationService, - IExperimentService, - IPythonSettings, -} from '../../../client/common/types'; +import { ExecutionInfo, IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { ICondaService, IComponentAdapter } from '../../../client/interpreter/contracts'; import { ServiceContainer } from '../../../client/ioc/container'; import { IServiceContainer } from '../../../client/ioc/types'; import { CondaEnvironmentInfo } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; -import { CondaService } from '../../../client/pythonEnvironments/discovery/locators/services/condaService'; +import { CondaService } from '../../../client/pythonEnvironments/common/environmentManagers/condaService'; suite('Common - Conda Installer', () => { let installer: CondaInstallerTest; @@ -29,7 +23,6 @@ suite('Common - Conda Installer', () => { let condaService: ICondaService; let condaLocatorService: IComponentAdapter; let configService: IConfigurationService; - let experimentService: IExperimentService; class CondaInstallerTest extends CondaInstaller { public async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { return super.getExecutionInfo(moduleName, resource); @@ -38,14 +31,11 @@ suite('Common - Conda Installer', () => { setup(() => { serviceContainer = mock(ServiceContainer); condaService = mock(CondaService); - experimentService = mock(); condaLocatorService = mock(); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(false); configService = mock(ConfigurationService); when(serviceContainer.get(ICondaService)).thenReturn(instance(condaService)); when(serviceContainer.get(IComponentAdapter)).thenReturn(instance(condaLocatorService)); when(serviceContainer.get(IConfigurationService)).thenReturn(instance(configService)); - when(serviceContainer.get(IExperimentService)).thenReturn(instance(experimentService)); installer = new CondaInstallerTest(instance(serviceContainer)); }); test('Name and priority', async () => { diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 8d786b22b5a1..50cbc4f5dfb7 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -21,7 +21,6 @@ import { } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../../../client/common/constants'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; import { ModuleInstaller } from '../../../client/common/installer/moduleInstaller'; import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; @@ -38,7 +37,6 @@ import { ExecutionInfo, IConfigurationService, IDisposableRegistry, - IExperimentService, IOutputChannel, IPythonSettings, Product, @@ -46,12 +44,7 @@ import { import { getNamesAndValues } from '../../../client/common/utils/enum'; import { Products } from '../../../client/common/utils/localize'; import { noop } from '../../../client/common/utils/misc'; -import { - IComponentAdapter, - ICondaLocatorService, - ICondaService, - IInterpreterService, -} from '../../../client/interpreter/contracts'; +import { IComponentAdapter, ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, ModuleInstallerType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; @@ -230,7 +223,6 @@ suite('Module Installer', () => { let configService: TypeMoq.IMock; let fs: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; - let experimentService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let installer: IModuleInstaller; const condaExecutable = 'my.exe'; @@ -247,17 +239,6 @@ suite('Module Installer', () => { .setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))) .returns(() => fs.object); - experimentService = TypeMoq.Mock.ofType(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(false)); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoveryWithoutFileWatching)) - .returns(() => Promise.resolve(false)); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) - .returns(() => experimentService.object); - disposables = []; serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())) @@ -273,10 +254,7 @@ suite('Module Installer', () => { const condaService = TypeMoq.Mock.ofType(); condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve(condaExecutable)); - const condaLocatorService = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) - .returns(() => condaLocatorService.object); + const condaLocatorService = TypeMoq.Mock.ofType(); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))) .returns(() => condaLocatorService.object); diff --git a/src/test/common/installer/pipEnvInstaller.unit.test.ts b/src/test/common/installer/pipEnvInstaller.unit.test.ts index 58a6206e1c5a..25b1b910daaa 100644 --- a/src/test/common/installer/pipEnvInstaller.unit.test.ts +++ b/src/test/common/installer/pipEnvInstaller.unit.test.ts @@ -8,18 +8,14 @@ import * as sinon from 'sinon'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; import { IWorkspaceService } from '../../../client/common/application/types'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { PipEnvInstaller } from '../../../client/common/installer/pipEnvInstaller'; -import { IExperimentService } from '../../../client/common/types'; -import { IInterpreterLocatorService, IInterpreterService, PIPENV_SERVICE } from '../../../client/interpreter/contracts'; +import { IInterpreterService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import * as pipEnvHelper from '../../../client/pythonEnvironments/common/environmentManagers/pipenv'; import { EnvironmentType } from '../../../client/pythonEnvironments/info'; suite('PipEnv installer', async () => { let serviceContainer: TypeMoq.IMock; - let locatorService: TypeMoq.IMock; - let experimentService: TypeMoq.IMock; let isPipenvEnvironmentRelatedToFolder: sinon.SinonStub; let workspaceService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; @@ -30,20 +26,6 @@ suite('PipEnv installer', async () => { serviceContainer = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); - locatorService = TypeMoq.Mock.ofType(); - experimentService = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(PIPENV_SERVICE))) - .returns(() => locatorService.object); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(false)); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) - .returns(() => experimentService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(PIPENV_SERVICE))) - .returns(() => locatorService.object); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) .returns(() => workspaceService.object); @@ -91,10 +73,7 @@ suite('PipEnv installer', async () => { test('If active environment is pipenv and is related to workspace folder, return true', async () => { const resource = Uri.parse('a'); - experimentService.reset(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(true)); + interpreterService .setup((p) => p.getActiveInterpreter(resource)) .returns(() => Promise.resolve({ envType: EnvironmentType.Pipenv, path: interpreterPath } as any)); @@ -108,10 +87,6 @@ suite('PipEnv installer', async () => { test('If active environment is not pipenv, return false', async () => { const resource = Uri.parse('a'); - experimentService.reset(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(true)); interpreterService .setup((p) => p.getActiveInterpreter(resource)) .returns(() => Promise.resolve({ envType: EnvironmentType.Conda, path: interpreterPath } as any)); @@ -125,10 +100,6 @@ suite('PipEnv installer', async () => { test('If active environment is pipenv but not related to workspace folder, return false', async () => { const resource = Uri.parse('a'); - experimentService.reset(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(true)); interpreterService .setup((p) => p.getActiveInterpreter(resource)) .returns(() => Promise.resolve({ envType: EnvironmentType.Pipenv, path: 'some random path' } as any)); @@ -139,11 +110,4 @@ suite('PipEnv installer', async () => { const result = await pipEnvInstaller.isSupported(resource); expect(result).to.equal(false, 'Should be false'); }); - - test('If InterpreterUri is Resource, and if resource does not contain pipEnv interpreters, return false', async () => { - const resource = Uri.parse('a'); - locatorService.setup((p) => p.getInterpreters(resource)).returns(() => Promise.resolve([])); - const result = await pipEnvInstaller.isSupported(resource); - expect(result).to.equal(false, 'Should be false'); - }); }); diff --git a/src/test/common/installer/poetryInstaller.unit.test.ts b/src/test/common/installer/poetryInstaller.unit.test.ts index 83265f956e8d..338a478b27c9 100644 --- a/src/test/common/installer/poetryInstaller.unit.test.ts +++ b/src/test/common/installer/poetryInstaller.unit.test.ts @@ -15,12 +15,11 @@ import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; import { PoetryInstaller } from '../../../client/common/installer/poetryInstaller'; import { ExecutionResult, ShellOptions } from '../../../client/common/process/types'; -import { ExecutionInfo, IConfigurationService, IExperimentService } from '../../../client/common/types'; +import { ExecutionInfo, IConfigurationService } from '../../../client/common/types'; import { ServiceContainer } from '../../../client/ioc/container'; import { IInterpreterService } from '../../../client/interpreter/contracts'; import { TEST_LAYOUT_ROOT } from '../../pythonEnvironments/common/commonTestConstants'; import * as externalDependencies from '../../../client/pythonEnvironments/common/externalDependencies'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { EnvironmentType } from '../../../client/pythonEnvironments/info'; suite('Module Installer - Poetry', () => { @@ -34,17 +33,14 @@ suite('Module Installer - Poetry', () => { let poetryInstaller: TestInstaller; let workspaceService: IWorkspaceService; let configurationService: IConfigurationService; - let experimentService: IExperimentService; let interpreterService: IInterpreterService; let serviceContainer: ServiceContainer; let shellExecute: sinon.SinonStub; setup(() => { serviceContainer = mock(ServiceContainer); - experimentService = mock(); interpreterService = mock(); when(serviceContainer.get(IInterpreterService)).thenReturn(instance(interpreterService)); - when(serviceContainer.get(IExperimentService)).thenReturn(instance(experimentService)); workspaceService = mock(WorkspaceService); configurationService = mock(ConfigurationService); @@ -127,7 +123,6 @@ suite('Module Installer - Poetry', () => { const uri = Uri.file(project1); const settings = mock(PythonSettings); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(true); when(interpreterService.getActiveInterpreter(anything())).thenResolve({ path: path.join(project1, '.venv', 'Scripts', 'python.exe'), envType: EnvironmentType.Poetry, @@ -146,7 +141,6 @@ suite('Module Installer - Poetry', () => { const uri = Uri.file(project1); const settings = mock(PythonSettings); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(true); when(interpreterService.getActiveInterpreter(anything())).thenResolve(undefined); when(configurationService.getSettings(anything())).thenReturn(instance(settings)); when(settings.poetryPath).thenReturn('poetry'); @@ -161,7 +155,6 @@ suite('Module Installer - Poetry', () => { const uri = Uri.file(project1); const settings = mock(PythonSettings); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(true); when(interpreterService.getActiveInterpreter(anything())).thenResolve({ path: path.join(project1, '.random', 'Scripts', 'python.exe'), envType: EnvironmentType.Poetry, @@ -180,7 +173,6 @@ suite('Module Installer - Poetry', () => { const uri = Uri.file(project1); const settings = mock(PythonSettings); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(true); when(interpreterService.getActiveInterpreter(anything())).thenResolve({ path: path.join(project1, '.venv', 'Scripts', 'python.exe'), envType: EnvironmentType.Pipenv, diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 7290b9eeae73..6ca49395dea8 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -1,6 +1,5 @@ import { expect, should as chaiShould, use as chaiUse } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as path from 'path'; import { SemVer } from 'semver'; import { instance, mock } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; @@ -32,7 +31,6 @@ import { WorkspaceService } from '../../client/common/application/workspace'; import { AsyncDisposableRegistry } from '../../client/common/asyncDisposableRegistry'; import { ConfigurationService } from '../../client/common/configuration/service'; import { EditorUtils } from '../../client/common/editor'; -import { DiscoveryVariants } from '../../client/common/experiments/groups'; import { ExperimentService } from '../../client/common/experiments/service'; import { ExtensionInsidersDailyChannelRule, @@ -66,7 +64,7 @@ import { PlatformService } from '../../client/common/platform/platformService'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { CurrentProcess } from '../../client/common/process/currentProcess'; import { ProcessLogger } from '../../client/common/process/logger'; -import { IProcessLogger, IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; +import { IProcessLogger, IProcessServiceFactory } from '../../client/common/process/types'; import { TerminalActivator } from '../../client/common/terminal/activator'; import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler'; import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash'; @@ -111,21 +109,13 @@ import { import { IMultiStepInputFactory, MultiStepInputFactory } from '../../client/common/utils/multiStepInput'; import { Architecture } from '../../client/common/utils/platform'; import { Random } from '../../client/common/utils/random'; -import { - ICondaService, - ICondaLocatorService, - IInterpreterLocatorService, - IInterpreterService, - INTERPRETER_LOCATOR_SERVICE, - PIPENV_SERVICE, - IComponentAdapter, -} from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterService, IComponentAdapter } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { JupyterExtensionDependencyManager } from '../../client/jupyter/jupyterExtensionDependencyManager'; import { EnvironmentType, PythonEnvironment } from '../../client/pythonEnvironments/info'; import { ImportTracker } from '../../client/telemetry/importTracker'; import { IImportTracker } from '../../client/telemetry/types'; -import { getExtensionSettings, PYTHON_PATH, rootWorkspaceUri } from '../common'; +import { PYTHON_PATH, rootWorkspaceUri } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; @@ -151,12 +141,10 @@ suite('Module Installer', () => { let ioc: UnitTestIocContainer; let mockTerminalService: TypeMoq.IMock; let condaService: TypeMoq.IMock; - let condaLocatorService: TypeMoq.IMock; - let experimentService: TypeMoq.IMock; + let condaLocatorService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let mockTerminalFactory: TypeMoq.IMock; - const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); suiteSetup(initializeTest); setup(async () => { chaiShould(); @@ -213,15 +201,7 @@ suite('Module Installer', () => { await ioc.registerMockInterpreterTypes(); condaService = TypeMoq.Mock.ofType(); - condaLocatorService = TypeMoq.Mock.ofType(); - experimentService = TypeMoq.Mock.ofType(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(false)); - ioc.serviceManager.addSingletonInstance( - ICondaLocatorService, - condaLocatorService.object, - ); + condaLocatorService = TypeMoq.Mock.ofType(); ioc.serviceManager.rebindInstance(ICondaService, condaService.object); interpreterService = TypeMoq.Mock.ofType(); ioc.serviceManager.rebindInstance(IInterpreterService, interpreterService.object); @@ -339,35 +319,11 @@ suite('Module Installer', () => { ConfigurationTarget.Workspace, ); } - async function getCurrentPythonPath(): Promise { - const { pythonPath } = getExtensionSettings(workspaceUri); - if (path.basename(pythonPath) === pythonPath) { - const pythonProc = await ioc.serviceContainer - .get(IPythonExecutionFactory) - .create({ resource: workspaceUri }); - return pythonProc.getExecutablePath().catch(() => pythonPath); - } - return pythonPath; - } test('Ensure pip is supported and conda is not', async () => { ioc.serviceManager.addSingletonInstance( IModuleInstaller, new MockModuleInstaller('mock', true), ); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator - .setup((p) => p.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve([])); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - mockInterpreterLocator.object, - INTERPRETER_LOCATOR_SERVICE, - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - TypeMoq.Mock.ofType().object, - PIPENV_SERVICE, - ); ioc.serviceManager.addSingletonInstance(ITerminalHelper, instance(mock(TerminalHelper))); const processService = (await ioc.serviceContainer @@ -402,34 +358,6 @@ suite('Module Installer', () => { IModuleInstaller, new MockModuleInstaller('mock', true), ); - const pythonPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator - .setup((p) => p.getInterpreters(TypeMoq.It.isAny())) - .returns(() => - Promise.resolve([ - { - ...info, - architecture: Architecture.Unknown, - companyDisplayName: '', - displayName: '', - envName: '', - path: pythonPath, - envType: EnvironmentType.Conda, - version: new SemVer('1.0.0'), - }, - ]), - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - mockInterpreterLocator.object, - INTERPRETER_LOCATOR_SERVICE, - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - TypeMoq.Mock.ofType().object, - PIPENV_SERVICE, - ); ioc.serviceManager.addSingletonInstance(ITerminalHelper, instance(mock(TerminalHelper))); const processService = (await ioc.serviceContainer @@ -463,14 +391,11 @@ suite('Module Installer', () => { configService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) + .setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))) .returns(() => condaLocatorService.object); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))) .returns(() => condaLocatorService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) - .returns(() => experimentService.object); condaService.setup((c) => c.isCondaAvailable()).returns(() => Promise.resolve(true)); condaLocatorService .setup((c) => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))) @@ -494,12 +419,6 @@ suite('Module Installer', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))) .returns(() => condaLocatorService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) - .returns(() => condaLocatorService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) - .returns(() => experimentService.object); condaService.setup((c) => c.isCondaAvailable()).returns(() => Promise.resolve(true)); condaLocatorService .setup((c) => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))) @@ -511,22 +430,6 @@ suite('Module Installer', () => { const resourceTestNameSuffix = resource ? ' with a resource' : ' without a resource'; test(`Validate pip install arguments ${resourceTestNameSuffix}`, async () => { - const interpreterPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator - .setup((p) => p.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve([{ ...info, path: interpreterPath, envType: EnvironmentType.Unknown }])); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - mockInterpreterLocator.object, - INTERPRETER_LOCATOR_SERVICE, - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - TypeMoq.Mock.ofType().object, - PIPENV_SERVICE, - ); - const interpreter: PythonEnvironment = { ...info, envType: EnvironmentType.Unknown, @@ -566,22 +469,6 @@ suite('Module Installer', () => { }); test(`Validate Conda install arguments ${resourceTestNameSuffix}`, async () => { - const interpreterPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator - .setup((p) => p.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve([{ ...info, path: interpreterPath, envType: EnvironmentType.Conda }])); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - mockInterpreterLocator.object, - INTERPRETER_LOCATOR_SERVICE, - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - TypeMoq.Mock.ofType().object, - PIPENV_SERVICE, - ); - const moduleName = 'xyz'; const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); @@ -607,18 +494,6 @@ suite('Module Installer', () => { }); test(`Validate pipenv install arguments ${resourceTestNameSuffix}`, async () => { - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator - .setup((p) => p.getInterpreters(TypeMoq.It.isAny())) - .returns(() => - Promise.resolve([{ ...info, path: 'interpreterPath', envType: EnvironmentType.VirtualEnv }]), - ); - ioc.serviceManager.rebindInstance( - IInterpreterLocatorService, - mockInterpreterLocator.object, - PIPENV_SERVICE, - ); - const moduleName = 'xyz'; const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); const pipInstaller = moduleInstallers.find((item) => item.displayName === 'pipenv')!; diff --git a/src/test/common/process/execFactory.test.ts b/src/test/common/process/execFactory.test.ts deleted file mode 100644 index 331be80f2b5b..000000000000 --- a/src/test/common/process/execFactory.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import * as TypeMoq from 'typemoq'; -import { Uri } from 'vscode'; -import { IFileSystem } from '../../../client/common/platform/types'; -import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; -import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; -import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types'; -import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { InterpreterVersionService } from '../../../client/interpreter/interpreterVersion'; -import { IServiceContainer } from '../../../client/ioc/types'; - -suite('PythonExecutableService', () => { - let serviceContainer: TypeMoq.IMock; - let configService: TypeMoq.IMock; - let procService: TypeMoq.IMock; - let procServiceFactory: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - const envVarsProvider = TypeMoq.Mock.ofType(); - procServiceFactory = TypeMoq.Mock.ofType(); - procService = TypeMoq.Mock.ofType(); - configService = TypeMoq.Mock.ofType(); - const fileSystem = TypeMoq.Mock.ofType(); - fileSystem.setup((f) => f.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))) - .returns(() => envVarsProvider.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory))) - .returns(() => procServiceFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService))) - .returns(() => configService.object); - procService.setup((x: any) => x.then).returns(() => undefined); - procServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(procService.object)); - envVarsProvider.setup((v) => v.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); - - const envActivationService = TypeMoq.Mock.ofType(); - envActivationService - .setup((e) => e.getActivatedEnvironmentVariables(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - envActivationService - .setup((e) => e.getActivatedEnvironmentVariables(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - envActivationService - .setup((e) => - e.getActivatedEnvironmentVariables(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - ) - .returns(() => Promise.resolve(undefined)); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IEnvironmentActivationService), TypeMoq.It.isAny())) - .returns(() => envActivationService.object); - }); - test('Ensure resource is used when getting configuration service settings (undefined resource)', async () => { - const pythonPath = `Python_Path_${new Date().toString()}`; - const pythonVersion = `Python_Version_${new Date().toString()}`; - const pythonSettings = TypeMoq.Mock.ofType(); - pythonSettings.setup((p) => p.pythonPath).returns(() => pythonPath); - configService.setup((c) => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => pythonSettings.object); - procService - .setup((p) => p.exec(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: pythonVersion })); - - const versionService = new InterpreterVersionService(procServiceFactory.object); - const version = await versionService.getVersion(pythonPath, ''); - - expect(version).to.be.equal(pythonVersion); - }); - test('Ensure resource is used when getting configuration service settings (defined resource)', async () => { - const resource = Uri.file('abc'); - const pythonPath = `Python_Path_${new Date().toString()}`; - const pythonVersion = `Python_Version_${new Date().toString()}`; - const pythonSettings = TypeMoq.Mock.ofType(); - pythonSettings.setup((p) => p.pythonPath).returns(() => pythonPath); - configService.setup((c) => c.getSettings(TypeMoq.It.isValue(resource))).returns(() => pythonSettings.object); - procService - .setup((p) => p.exec(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: pythonVersion })); - - const versionService = new InterpreterVersionService(procServiceFactory.object); - const version = await versionService.getVersion(pythonPath, ''); - - expect(version).to.be.equal(pythonVersion); - }); -}); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 09249c1f0f9d..b32b116bb6c1 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -31,9 +31,8 @@ import { IEnvironmentActivationService } from '../../../client/interpreter/activ import { IComponentAdapter, ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { ServiceContainer } from '../../../client/ioc/container'; -import { CondaService } from '../../../client/pythonEnvironments/discovery/locators/services/condaService'; +import { CondaService } from '../../../client/pythonEnvironments/common/environmentManagers/condaService'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; -import * as WindowsStoreInterpreter from '../../../client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { IInterpreterAutoSelectionService } from '../../../client/interpreter/autoSelection/types'; const pythonInterpreter: PythonEnvironment = { @@ -85,7 +84,6 @@ suite('Process - PythonExecutionFactory', () => { let interpreterService: IInterpreterService; let pyenvs: IComponentAdapter; let executionService: typemoq.IMock; - let isWindowsStoreInterpreterStub: sinon.SinonStub; let autoSelection: IInterpreterAutoSelectionService; let interpreterPathExpHelper: IInterpreterPathProxyService; setup(() => { @@ -142,9 +140,6 @@ suite('Process - PythonExecutionFactory', () => { instance(autoSelection), instance(interpreterPathExpHelper), ); - - isWindowsStoreInterpreterStub = sinon.stub(WindowsStoreInterpreter, 'isWindowsStoreInterpreter'); - isWindowsStoreInterpreterStub.resolves(true); }); teardown(() => sinon.restore()); @@ -262,23 +257,6 @@ suite('Process - PythonExecutionFactory', () => { assert.equal(createInvoked, false); }); - test("Ensure `create` returns a WindowsStorePythonProcess instance if it's a windows store intepreter path and we're in the discovery experiment", async () => { - const pythonPath = 'path/to/python'; - const pythonSettings = mock(PythonSettings); - - when(processFactory.create(resource)).thenResolve(processService.object); - when(pythonSettings.pythonPath).thenReturn(pythonPath); - when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - - const service = await factory.create({ resource }); - - expect(service).to.not.equal(undefined); - verify(processFactory.create(resource)).once(); - verify(pythonSettings.pythonPath).once(); - verify(pyenvs.isWindowsStoreInterpreter(pythonPath)).once(); - sinon.assert.notCalled(isWindowsStoreInterpreterStub); - }); - test('Ensure `create` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async function () { return this.skip(); diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index 8cac5418fb92..eb735b620aae 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -8,7 +8,6 @@ import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { Disposable } from 'vscode'; import { TerminalManager } from '../../../client/common/application/terminalManager'; -import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import '../../../client/common/extensions'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; @@ -25,7 +24,6 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../../../ import { IConfigurationService, IDisposableRegistry, - IExperimentService, IPythonSettings, ITerminalSettings, } from '../../../client/common/types'; @@ -45,7 +43,6 @@ suite('Terminal Environment Activation conda', () => { let processService: TypeMoq.IMock; let procServiceFactory: TypeMoq.IMock; let condaService: TypeMoq.IMock; - let experimentService: TypeMoq.IMock; let componentAdapter: TypeMoq.IMock; let configService: TypeMoq.IMock; let conda: string; @@ -59,10 +56,6 @@ suite('Terminal Environment Activation conda', () => { .setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())) .returns(() => disposables); - experimentService = TypeMoq.Mock.ofType(); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(false)); componentAdapter = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); @@ -70,9 +63,6 @@ suite('Terminal Environment Activation conda', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IComponentAdapter))) .returns(() => componentAdapter.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) - .returns(() => experimentService.object); condaService = TypeMoq.Mock.ofType(); condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve(conda)); bash = mock(Bash); diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts index fa20b6db1b34..e9c04ca55da5 100644 --- a/src/test/format/extension.sort.test.ts +++ b/src/test/format/extension.sort.test.ts @@ -10,7 +10,7 @@ import { ICondaService, IInterpreterService } from '../../client/interpreter/con import { InterpreterService } from '../../client/interpreter/interpreterService'; import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; import { ISortImportsEditingProvider } from '../../client/providers/types'; -import { CondaService } from '../../client/pythonEnvironments/discovery/locators/services/condaService'; +import { CondaService } from '../../client/pythonEnvironments/common/environmentManagers/condaService'; import { updateSetting } from '../common'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, TEST_TIMEOUT } from '../initialize'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; diff --git a/src/test/install/channelManager.channels.test.ts b/src/test/install/channelManager.channels.test.ts index 54c963fd7364..a2e029e506c8 100644 --- a/src/test/install/channelManager.channels.test.ts +++ b/src/test/install/channelManager.channels.test.ts @@ -3,51 +3,28 @@ import * as assert from 'assert'; import { Container } from 'inversify'; -import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { IApplicationShell } from '../../client/common/application/types'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; import { IModuleInstaller } from '../../client/common/installer/types'; import { Product } from '../../client/common/types'; -import { Architecture } from '../../client/common/utils/platform'; import { IInterpreterAutoSelectionService, IInterpreterAutoSelectionProxyService, } from '../../client/interpreter/autoSelection/types'; -import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../client/interpreter/contracts'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer } from '../../client/ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../client/pythonEnvironments/info'; import { MockAutoSelectionService } from '../mocks/autoSelector'; -const info: PythonEnvironment = { - architecture: Architecture.Unknown, - companyDisplayName: '', - displayName: '', - envName: '', - path: '', - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - sysPrefix: '', - sysVersion: '', -}; - suite('Installation - installation channels', () => { let serviceManager: ServiceManager; let serviceContainer: IServiceContainer; - let pipEnv: TypeMoq.IMock; setup(() => { const cont = new Container(); serviceManager = new ServiceManager(cont); serviceContainer = new ServiceContainer(cont); - pipEnv = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance( - IInterpreterLocatorService, - pipEnv.object, - PIPENV_SERVICE, - ); serviceManager.addSingleton( IInterpreterAutoSelectionService, MockAutoSelectionService, @@ -84,13 +61,6 @@ suite('Installation - installation channels', () => { mockInstaller(true, '3'); const pipenvInstaller = mockInstaller(true, 'pipenv', 10); - const interpreter: PythonEnvironment = { - ...info, - path: 'pipenv', - envType: EnvironmentType.VirtualEnv, - }; - pipEnv.setup((x) => x.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([interpreter])); - const cm = new InstallationChannelManager(serviceContainer); const channels = await cm.getInstallationChannels(); assert.equal(channels.length, 1, 'Incorrect number of channels'); diff --git a/src/test/interpreters/currentPathService.unit.test.ts b/src/test/interpreters/currentPathService.unit.test.ts deleted file mode 100644 index 6985f3cc36e4..000000000000 --- a/src/test/interpreters/currentPathService.unit.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { SemVer } from 'semver'; -import * as TypeMoq from 'typemoq'; -import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; -import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; -import { - IConfigurationService, - IPersistentState, - IPersistentStateFactory, - IPythonSettings, -} from '../../client/common/types'; -import { OSType } from '../../client/common/utils/platform'; -import { IInterpreterVersionService } from '../../client/interpreter/contracts'; -import { InterpreterHelper } from '../../client/interpreter/helpers'; -import { IPythonInPathCommandProvider } from '../../client/interpreter/locators/types'; -import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; -import { IServiceContainer } from '../../client/ioc/types'; -import { - CurrentPathService, - PythonInPathCommandProvider, -} from '../../client/pythonEnvironments/discovery/locators/services/currentPathService'; -import { EnvironmentType, PythonEnvironment } from '../../client/pythonEnvironments/info'; - -suite('Interpreters CurrentPath Service', () => { - let processService: TypeMoq.IMock; - let fileSystem: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let virtualEnvironmentManager: TypeMoq.IMock; - let interpreterHelper: TypeMoq.IMock; - let pythonSettings: TypeMoq.IMock; - let currentPathService: CurrentPathService; - let persistentState: TypeMoq.IMock>; - let platformService: TypeMoq.IMock; - let pythonInPathCommandProvider: IPythonInPathCommandProvider; - setup(async () => { - processService = TypeMoq.Mock.ofType(); - virtualEnvironmentManager = TypeMoq.Mock.ofType(); - interpreterHelper = TypeMoq.Mock.ofType(); - const configurationService = TypeMoq.Mock.ofType(); - pythonSettings = TypeMoq.Mock.ofType(); - configurationService.setup((c) => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - const persistentStateFactory = TypeMoq.Mock.ofType(); - persistentState = TypeMoq.Mock.ofType>(); - processService.setup((x: any) => x.then).returns(() => undefined); - persistentState.setup((p) => p.value).returns(() => undefined as any); - persistentState.setup((p) => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); - fileSystem = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - persistentStateFactory - .setup((p) => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => persistentState.object); - const procServiceFactory = TypeMoq.Mock.ofType(); - procServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)); - - serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager), TypeMoq.It.isAny())) - .returns(() => virtualEnvironmentManager.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())) - .returns(() => interpreterHelper.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())) - .returns(() => fileSystem.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())) - .returns(() => persistentStateFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) - .returns(() => configurationService.object); - pythonInPathCommandProvider = new PythonInPathCommandProvider(platformService.object); - currentPathService = new CurrentPathService( - interpreterHelper.object, - procServiceFactory.object, - pythonInPathCommandProvider, - serviceContainer.object, - ); - }); - - [true, false].forEach((isWindows) => { - test(`Interpreters that do not exist on the file system are not excluded from the list (${ - isWindows ? 'windows' : 'not windows' - })`, async () => { - // Specific test for 1305 - const version = new SemVer('1.0.0'); - platformService.setup((p) => p.isWindows).returns(() => isWindows); - platformService.setup((p) => p.osType).returns(() => (isWindows ? OSType.Windows : OSType.Linux)); - interpreterHelper - .setup((v) => v.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version })); - - const execArgs = ['-c', 'import sys;print(sys.executable)']; - pythonSettings.setup((p) => p.pythonPath).returns(() => 'root:Python'); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('root:Python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny()), - ) - .returns(() => Promise.resolve({ stdout: 'c:/root:python' })) - .verifiable(TypeMoq.Times.once()); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'c:/python1' })) - .verifiable(TypeMoq.Times.once()); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('python2'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'c:/python2' })) - .verifiable(TypeMoq.Times.once()); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('python3'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'c:/python3' })) - .verifiable(TypeMoq.Times.once()); - - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue('c:/root:python'))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue('c:/python1'))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue('c:/python2'))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue('c:/python3'))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - const interpreters = await currentPathService.getInterpreters(); - processService.verifyAll(); - fileSystem.verifyAll(); - - expect(interpreters).to.be.of.length(2); - expect(interpreters).to.deep.include({ version, path: 'c:/root:python', envType: EnvironmentType.Unknown }); - expect(interpreters).to.deep.include({ version, path: 'c:/python3', envType: EnvironmentType.Unknown }); - }); - }); -}); diff --git a/src/test/interpreters/interpreterService.unit.test.ts b/src/test/interpreters/interpreterService.unit.test.ts index 3d49d4082a37..0a7bbe7b6b47 100644 --- a/src/test/interpreters/interpreterService.unit.test.ts +++ b/src/test/interpreters/interpreterService.unit.test.ts @@ -28,15 +28,8 @@ import { IInterpreterAutoSelectionProxyService, } from '../../client/interpreter/autoSelection/types'; import { IPythonPathUpdaterServiceManager } from '../../client/interpreter/configuration/types'; -import { - IComponentAdapter, - IInterpreterDisplay, - IInterpreterHelper, - IInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE, -} from '../../client/interpreter/contracts'; +import { IComponentAdapter, IInterpreterDisplay, IInterpreterHelper } from '../../client/interpreter/contracts'; import { InterpreterService } from '../../client/interpreter/interpreterService'; -import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { PYTHON_PATH } from '../common'; @@ -52,12 +45,10 @@ suite('Interpreters service', () => { let updater: TypeMoq.IMock; let pyenvs: TypeMoq.IMock; let helper: TypeMoq.IMock; - let locator: TypeMoq.IMock; let workspace: TypeMoq.IMock; let config: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; let interpreterDisplay: TypeMoq.IMock; - let virtualEnvMgr: TypeMoq.IMock; let persistentStateFactory: TypeMoq.IMock; let pythonExecutionFactory: TypeMoq.IMock; let pythonExecutionService: TypeMoq.IMock; @@ -76,12 +67,10 @@ suite('Interpreters service', () => { updater = TypeMoq.Mock.ofType(); pyenvs = TypeMoq.Mock.ofType(); helper = TypeMoq.Mock.ofType(); - locator = TypeMoq.Mock.ofType(); workspace = TypeMoq.Mock.ofType(); config = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); interpreterDisplay = TypeMoq.Mock.ofType(); - virtualEnvMgr = TypeMoq.Mock.ofType(); persistentStateFactory = TypeMoq.Mock.ofType(); pythonExecutionFactory = TypeMoq.Mock.ofType(); pythonExecutionService = TypeMoq.Mock.ofType(); @@ -113,11 +102,6 @@ suite('Interpreters service', () => { updater.object, ); serviceManager.addSingletonInstance(IWorkspaceService, workspace.object); - serviceManager.addSingletonInstance( - IInterpreterLocatorService, - locator.object, - INTERPRETER_LOCATOR_SERVICE, - ); serviceManager.addSingletonInstance(IFileSystem, fileSystem.object); serviceManager.addSingletonInstance(IExperimentService, experimentService.object); serviceManager.addSingletonInstance( @@ -125,10 +109,6 @@ suite('Interpreters service', () => { interpreterPathService.object, ); serviceManager.addSingletonInstance(IInterpreterDisplay, interpreterDisplay.object); - serviceManager.addSingletonInstance( - IVirtualEnvironmentManager, - virtualEnvMgr.object, - ); serviceManager.addSingletonInstance( IPersistentStateFactory, persistentStateFactory.object, diff --git a/src/test/interpreters/interpreterVersion.unit.test.ts b/src/test/interpreters/interpreterVersion.unit.test.ts deleted file mode 100644 index 39b56aced700..000000000000 --- a/src/test/interpreters/interpreterVersion.unit.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { assert, expect } from 'chai'; -import * as path from 'path'; -import * as typeMoq from 'typemoq'; -import '../../client/common/extensions'; -import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; -import { IInterpreterVersionService } from '../../client/interpreter/contracts'; -import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; - -suite('InterpreterVersionService', () => { - let processService: typeMoq.IMock; - let interpreterVersionService: IInterpreterVersionService; - - setup(() => { - const processFactory = typeMoq.Mock.ofType(); - processService = typeMoq.Mock.ofType(); - - processService.setup((p: any) => p.then).returns(() => undefined); - - processFactory.setup((p) => p.create()).returns(() => Promise.resolve(processService.object)); - interpreterVersionService = new InterpreterVersionService(processFactory.object); - }); - - suite('getPipVersion', () => { - test('Must return the pip Version.', async () => { - const pythonPath = path.join('a', 'b', 'python'); - const pipVersion = '1.2.3'; - processService - .setup((p) => - p.exec( - typeMoq.It.isValue(pythonPath), - typeMoq.It.isValue(['-c', 'import pip; print(pip.__version__)']), - typeMoq.It.isAny(), - ), - ) - .returns(() => Promise.resolve({ stdout: pipVersion })) - .verifiable(typeMoq.Times.once()); - - const pyVersion = await interpreterVersionService.getPipVersion(pythonPath); - assert.equal(pyVersion, pipVersion, 'Incorrect version'); - }); - - test('Must throw an exception when pip version cannot be determined', async () => { - const pythonPath = path.join('a', 'b', 'python'); - processService - .setup((p) => - p.exec( - typeMoq.It.isValue(pythonPath), - typeMoq.It.isValue(['-c', 'import pip; print(pip.__version__)']), - typeMoq.It.isAny(), - ), - ) - .returns(() => Promise.reject('error')) - .verifiable(typeMoq.Times.once()); - - const pipVersionPromise = interpreterVersionService.getPipVersion(pythonPath); - await expect(pipVersionPromise).to.be.rejectedWith(); - }); - }); -}); diff --git a/src/test/interpreters/mocks.ts b/src/test/interpreters/mocks.ts index d5727d2b089f..bdc0c254ae31 100644 --- a/src/test/interpreters/mocks.ts +++ b/src/test/interpreters/mocks.ts @@ -2,7 +2,6 @@ import { injectable } from 'inversify'; import { IRegistry, RegistryHive } from '../../client/common/platform/types'; import { IPersistentState } from '../../client/common/types'; import { Architecture } from '../../client/common/utils/platform'; -import { IInterpreterVersionService } from '../../client/interpreter/contracts'; @injectable() export class MockRegistry implements IRegistry { @@ -43,23 +42,6 @@ export class MockRegistry implements IRegistry { } } -@injectable() -export class MockInterpreterVersionProvider implements IInterpreterVersionService { - constructor( - private displayName: string, - private useDefaultDisplayName: boolean = false, - private pipVersionPromise?: Promise, - ) {} - public async getVersion(_pythonPath: string, defaultDisplayName: string): Promise { - return this.useDefaultDisplayName ? Promise.resolve(defaultDisplayName) : Promise.resolve(this.displayName); - } - public async getPipVersion(_pythonPath: string): Promise { - return this.pipVersionPromise!; - } - - public dispose() {} -} - export class MockState implements IPersistentState { constructor(public data: any) {} diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index b0202f3c91a7..40664764fd4c 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -30,7 +30,6 @@ import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, - IInterpreterVersionService, IShebangCodeLensProvider, } from '../../client/interpreter/contracts'; import { InterpreterDisplay } from '../../client/interpreter/display'; @@ -38,7 +37,6 @@ import { InterpreterLocatorProgressStatubarHandler } from '../../client/interpre import { ShebangCodeLensProvider } from '../../client/interpreter/display/shebangCodeLensProvider'; import { InterpreterHelper } from '../../client/interpreter/helpers'; import { InterpreterService } from '../../client/interpreter/interpreterService'; -import { InterpreterVersionService } from '../../client/interpreter/interpreterVersion'; import { registerTypes } from '../../client/interpreter/serviceRegistry'; import { CondaInheritEnvPrompt } from '../../client/interpreter/virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentPrompt } from '../../client/interpreter/virtualEnvs/virtualEnvPrompt'; @@ -56,8 +54,6 @@ suite('Interpreters - Service Registry', () => { [IExtensionActivationService, VirtualEnvironmentPrompt], - [IInterpreterVersionService, InterpreterVersionService], - [IInterpreterService, InterpreterService], [IInterpreterDisplay, InterpreterDisplay], diff --git a/src/test/interpreters/virtualEnvManager.unit.test.ts b/src/test/interpreters/virtualEnvManager.unit.test.ts deleted file mode 100644 index 8fe11bd4e369..000000000000 --- a/src/test/interpreters/virtualEnvManager.unit.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../client/common/application/types'; -import { IFileSystem } from '../../client/common/platform/types'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { IInterpreterLocatorService, IPipEnvService, PIPENV_SERVICE } from '../../client/interpreter/contracts'; -import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -import { IServiceContainer } from '../../client/ioc/types'; - -suite('Virtual environment manager', () => { - const virtualEnvFolderName = 'virtual Env Folder Name'; - const pythonPath = path.join('a', 'b', virtualEnvFolderName, 'd', 'python'); - - test('Plain Python environment suffix', async () => testSuffix(virtualEnvFolderName)); - test('Plain Python environment suffix with workspace Uri', async () => - testSuffix(virtualEnvFolderName, false, Uri.file(path.join('1', '2', '3', '4')))); - test('Plain Python environment suffix with PipEnv', async () => - testSuffix('workspaceName', true, Uri.file(path.join('1', '2', '3', 'workspaceName')))); - - test('Use environment folder as env name', async () => { - const serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IPipEnvService))) - .returns(() => TypeMoq.Mock.ofType().object); - const workspaceService = TypeMoq.Mock.ofType(); - workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - - const venvManager = new VirtualEnvironmentManager(serviceContainer.object); - const name = await venvManager.getEnvironmentName(pythonPath); - - expect(name).to.be.equal(virtualEnvFolderName); - }); - - test('Use workspace name as env name', async () => { - const serviceContainer = TypeMoq.Mock.ofType(); - const pipEnvService = TypeMoq.Mock.ofType(); - pipEnvService - .setup((p) => p.isRelatedPipEnvironment(TypeMoq.It.isAny(), TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IProcessServiceFactory))) - .returns(() => TypeMoq.Mock.ofType().object); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(PIPENV_SERVICE))) - .returns(() => pipEnvService.object); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IFileSystem))) - .returns(() => TypeMoq.Mock.ofType().object); - const workspaceUri = Uri.file(path.join('root', 'sub', 'wkspace folder')); - const workspaceFolder: WorkspaceFolder = { name: 'wkspace folder', index: 0, uri: workspaceUri }; - const workspaceService = TypeMoq.Mock.ofType(); - workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => true); - workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - - const venvManager = new VirtualEnvironmentManager(serviceContainer.object); - const name = await venvManager.getEnvironmentName(pythonPath); - - expect(name).to.be.equal(path.basename(workspaceUri.fsPath)); - pipEnvService.verifyAll(); - }); - - async function testSuffix(expectedEnvName: string, isPipEnvironment: boolean = false, resource?: Uri) { - const serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IProcessServiceFactory))) - .returns(() => TypeMoq.Mock.ofType().object); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IFileSystem))) - .returns(() => TypeMoq.Mock.ofType().object); - const pipEnvService = TypeMoq.Mock.ofType(); - pipEnvService - .setup((w) => w.isRelatedPipEnvironment(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(isPipEnvironment)); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(PIPENV_SERVICE))) - .returns(() => pipEnvService.object); - const workspaceService = TypeMoq.Mock.ofType(); - workspaceService.setup((w) => w.hasWorkspaceFolders).returns(() => false); - if (resource) { - const workspaceFolder = TypeMoq.Mock.ofType(); - workspaceFolder.setup((w) => w.uri).returns(() => resource); - workspaceService - .setup((w) => w.getWorkspaceFolder(TypeMoq.It.isAny())) - .returns(() => workspaceFolder.object); - } - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - - const venvManager = new VirtualEnvironmentManager(serviceContainer.object); - - const name = await venvManager.getEnvironmentName(pythonPath, resource); - expect(name).to.be.equal(expectedEnvName, 'Virtual envrironment name suffix is incorrect.'); - } -}); diff --git a/src/test/interpreters/virtualEnvs/index.unit.test.ts b/src/test/interpreters/virtualEnvs/index.unit.test.ts deleted file mode 100644 index ea602417fcff..000000000000 --- a/src/test/interpreters/virtualEnvs/index.unit.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../client/common/application/types'; -import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; -import { ITerminalActivationCommandProvider } from '../../../client/common/terminal/types'; -import { ICurrentProcess, IPathUtils } from '../../../client/common/types'; -import { IInterpreterLocatorService, IPipEnvService, PIPENV_SERVICE } from '../../../client/interpreter/contracts'; -import { VirtualEnvironmentManager } from '../../../client/interpreter/virtualEnvs'; -import { IServiceContainer } from '../../../client/ioc/types'; - -suite('Virtual Environment Manager', () => { - let process: TypeMoq.IMock; - let processService: TypeMoq.IMock; - let pathUtils: TypeMoq.IMock; - let virtualEnvMgr: VirtualEnvironmentManager; - let fs: TypeMoq.IMock; - let workspace: TypeMoq.IMock; - let pipEnvService: TypeMoq.IMock; - let terminalActivation: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - - setup(() => { - const serviceContainer = TypeMoq.Mock.ofType(); - process = TypeMoq.Mock.ofType(); - processService = TypeMoq.Mock.ofType(); - const processFactory = TypeMoq.Mock.ofType(); - pathUtils = TypeMoq.Mock.ofType(); - fs = TypeMoq.Mock.ofType(); - workspace = TypeMoq.Mock.ofType(); - pipEnvService = TypeMoq.Mock.ofType(); - terminalActivation = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - - processService.setup((p) => (p as any).then).returns(() => undefined); - processFactory.setup((p) => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory))) - .returns(() => processFactory.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ICurrentProcess))).returns(() => process.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IPathUtils))).returns(() => pathUtils.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fs.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspace.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(PIPENV_SERVICE))) - .returns(() => pipEnvService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ITerminalActivationCommandProvider), TypeMoq.It.isAny())) - .returns(() => terminalActivation.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())) - .returns(() => platformService.object); - - virtualEnvMgr = new VirtualEnvironmentManager(serviceContainer.object); - }); - - test('Get PyEnv Root from PYENV_ROOT', async () => { - process - .setup((p) => p.env) - .returns(() => { - return { PYENV_ROOT: 'yes' }; - }) - .verifiable(TypeMoq.Times.once()); - - const pyenvRoot = await virtualEnvMgr.getPyEnvRoot(); - - process.verifyAll(); - expect(pyenvRoot).to.equal('yes'); - }); - - test('Get PyEnv Root from current PYENV_ROOT', async () => { - process - .setup((p) => p.env) - .returns(() => { - return {}; - }) - .verifiable(TypeMoq.Times.once()); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pyenv'), TypeMoq.It.isValue(['root']))) - .returns(() => Promise.resolve({ stdout: 'PROC' })) - .verifiable(TypeMoq.Times.once()); - - const pyenvRoot = await virtualEnvMgr.getPyEnvRoot(); - - process.verifyAll(); - processService.verifyAll(); - expect(pyenvRoot).to.equal('PROC'); - }); - - test('Get default PyEnv Root path', async () => { - process - .setup((p) => p.env) - .returns(() => { - return {}; - }) - .verifiable(TypeMoq.Times.once()); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pyenv'), TypeMoq.It.isValue(['root']))) - .returns(() => Promise.resolve({ stdout: '', stderr: 'err' })) - .verifiable(TypeMoq.Times.once()); - pathUtils - .setup((p) => p.home) - .returns(() => 'HOME') - .verifiable(TypeMoq.Times.once()); - const pyenvRoot = await virtualEnvMgr.getPyEnvRoot(); - - process.verifyAll(); - processService.verifyAll(); - expect(pyenvRoot).to.equal(path.join('HOME', '.pyenv')); - }); - - test('Get Environment Type, detects venv', async () => { - const pythonPath = path.join('a', 'b', 'c', 'python'); - const dir = path.dirname(pythonPath); - - fs.setup((f) => f.fileExists(TypeMoq.It.isValue(path.join(dir, 'pyvenv.cfg')))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isVenvEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(true, 'invalid value'); - fs.verifyAll(); - }); - test('Get Environment Type, does not detect venv incorrectly', async () => { - const pythonPath = path.join('a', 'b', 'c', 'python'); - const dir = path.dirname(pythonPath); - - fs.setup((f) => f.fileExists(TypeMoq.It.isValue(path.join(dir, 'pyvenv.cfg')))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isVenvEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(false, 'invalid value'); - fs.verifyAll(); - }); - - test('Get Environment Type, detects pyenv', async () => { - const pythonPath = path.join('py-env-root', 'b', 'c', 'python'); - - process - .setup((p) => p.env) - .returns(() => { - return { PYENV_ROOT: path.join('py-env-root', 'b') }; - }) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isPyEnvEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(true, 'invalid value'); - process.verifyAll(); - }); - - test('Get Environment Type, does not detect pyenv incorrectly', async () => { - const pythonPath = path.join('a', 'b', 'c', 'python'); - - process - .setup((p) => p.env) - .returns(() => { - return { PYENV_ROOT: path.join('py-env-root', 'b') }; - }) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isPyEnvEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(false, 'invalid value'); - process.verifyAll(); - }); - - test('Get Environment Type, detects pipenv', async () => { - const pythonPath = path.join('x', 'b', 'c', 'python'); - workspace - .setup((w) => w.hasWorkspaceFolders) - .returns(() => true) - .verifiable(TypeMoq.Times.atLeastOnce()); - const ws = [{ uri: Uri.file('x') }]; - workspace - .setup((w) => w.workspaceFolders) - .returns(() => ws as any) - .verifiable(TypeMoq.Times.atLeastOnce()); - pipEnvService - .setup((p) => p.isRelatedPipEnvironment(TypeMoq.It.isAny(), TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isPipEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(true, 'invalid value'); - workspace.verifyAll(); - pipEnvService.verifyAll(); - }); - - test('Get Environment Type, does not detect pipenv incorrectly', async () => { - const pythonPath = path.join('x', 'b', 'c', 'python'); - workspace - .setup((w) => w.hasWorkspaceFolders) - .returns(() => true) - .verifiable(TypeMoq.Times.atLeastOnce()); - const ws = [{ uri: Uri.file('x') }]; - workspace - .setup((w) => w.workspaceFolders) - .returns(() => ws as any) - .verifiable(TypeMoq.Times.atLeastOnce()); - pipEnvService - .setup((p) => p.isRelatedPipEnvironment(TypeMoq.It.isAny(), TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - - const isRecognized = await virtualEnvMgr.isPipEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(false, 'invalid value'); - workspace.verifyAll(); - pipEnvService.verifyAll(); - }); - - for (const isWindows of [true, false]) { - const testTitleSuffix = `(${isWindows ? 'On Windows' : 'Non-Windows'}})`; - - test(`Get Environment Type, detects virtualenv ${testTitleSuffix}`, async () => { - const pythonPath = path.join('x', 'b', 'c', 'python'); - platformService - .setup((p) => p.isWindows) - .returns(() => isWindows) - .verifiable(TypeMoq.Times.once()); - fs.setup((f) => f.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const isRecognized = await virtualEnvMgr.isVirtualEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(true, 'invalid value'); - platformService.verifyAll(); - }); - - test(`Get Environment Type, does not detect virtualenv incorrectly ${testTitleSuffix}`, async () => { - const pythonPath = path.join('x', 'b', 'c', 'python'); - platformService - .setup((p) => p.isWindows) - .returns(() => isWindows) - .verifiable(TypeMoq.Times.once()); - fs.setup((f) => f.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const isRecognized = await virtualEnvMgr.isVirtualEnvironment(pythonPath); - - expect(isRecognized).to.be.equal(false, 'invalid value'); - platformService.verifyAll(); - }); - } -}); diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index fd083214d8fe..b100c4957887 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -26,26 +26,15 @@ import { IPythonExecutionFactory, IPythonToolExecutionService, } from '../../client/common/process/types'; -import { - IConfigurationService, - IDisposableRegistry, - IExperimentService, - IInterpreterPathProxyService, -} from '../../client/common/types'; +import { IConfigurationService, IDisposableRegistry, IInterpreterPathProxyService } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; -import { - IComponentAdapter, - ICondaLocatorService, - ICondaService, - IInterpreterService, -} from '../../client/interpreter/contracts'; +import { IComponentAdapter, ICondaService, IInterpreterService } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { LINTERID_BY_PRODUCT } from '../../client/linters/constants'; import { ILintMessage, LinterId, LintMessageSeverity } from '../../client/linters/types'; import { deleteFile, PYTHON_PATH } from '../common'; import { BaseTestFixture, getLinterID, getProductName, newMockDocument, throwUnknownProduct } from './common'; -import { DiscoveryVariants } from '../../client/common/experiments/groups'; import { IInterpreterAutoSelectionService } from '../../client/interpreter/autoSelection/types'; const workspaceDir = path.join(__dirname, '..', '..', '..', 'src', 'test'); @@ -707,13 +696,6 @@ class TestFixture extends BaseTestFixture { .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())) .returns(() => interpreterService.object); - const condaLocatorService = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) - .returns(() => condaLocatorService.object); - condaLocatorService - .setup((c) => c.getCondaEnvironment(TypeMoq.It.isAnyString())) - .returns(() => Promise.resolve(undefined)); const condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); condaService.setup((c) => c.getCondaVersion()).returns(() => Promise.resolve(undefined)); condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve('conda')); @@ -732,11 +714,6 @@ class TestFixture extends BaseTestFixture { ); const pyenvs: IComponentAdapter = mock(); - const experimentService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); - experimentService - .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) - .returns(() => Promise.resolve(false)); - const autoSelection = mock(); const interpreterPathExpHelper = mock(); when(interpreterPathExpHelper.get(anything())).thenReturn('selected interpreter path'); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts index 57def7eb618a..ad9438f9aba8 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts @@ -11,18 +11,14 @@ import { FSWatcherKind, FSWatchingLocator, } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator'; -import * as externalDeps from '../../../../../client/pythonEnvironments/common/externalDependencies'; import * as binWatcher from '../../../../../client/pythonEnvironments/common/pythonBinariesWatcher'; suite('File System Watching Locator Tests', () => { const baseDir = '/this/is/a/fake/path'; const callback = async () => Promise.resolve(PythonEnvKind.System); - let inExperimentStub: sinon.SinonStub; let watchLocationStub: sinon.SinonStub; setup(() => { - inExperimentStub = sinon.stub(externalDeps, 'inExperiment'); - watchLocationStub = sinon.stub(binWatcher, 'watchLocationForPythonBinaries'); watchLocationStub.resolves(new Disposables()); }); @@ -84,36 +80,25 @@ suite('File System Watching Locator Tests', () => { } const watcherKinds = [FSWatcherKind.Global, FSWatcherKind.Workspace]; - const watcherExperiment = [true, false]; const opts = { envStructure, }; watcherKinds.forEach((watcherKind) => { - suite(`watching ${FSWatcherKind[watcherKind]}`, () => { - watcherExperiment.forEach((experiment) => { - test(`${experiment ? '' : 'not '}in experiment`, async () => { - inExperimentStub.resolves(experiment); - - const testWatcher = new TestWatcher(watcherKind, opts); - await testWatcher.initialize(); + test(`watching ${FSWatcherKind[watcherKind]}`, async () => { + const testWatcher = new TestWatcher(watcherKind, opts); + await testWatcher.initialize(); - // Watcher should be called for all workspace locators. For global locators it should be called only if - // the watcher experiment allows it - if ( - (watcherKind === FSWatcherKind.Global && experiment) || - watcherKind === FSWatcherKind.Workspace - ) { - assert.equal(watchLocationStub.callCount, expected.length); - expected.forEach((glob) => { - assert.ok(watchLocationStub.calledWithMatch(baseDir, sinon.match.any, glob)); - }); - } else { - assert.ok(watchLocationStub.notCalled); - } + // Watcher should be called for all workspace locators. For global locators it should never be called. + if (watcherKind === FSWatcherKind.Workspace) { + assert.equal(watchLocationStub.callCount, expected.length); + expected.forEach((glob) => { + assert.ok(watchLocationStub.calledWithMatch(baseDir, sinon.match.any, glob)); }); - }); + } else if (watcherKind === FSWatcherKind.Global) { + assert.ok(watchLocationStub.notCalled); + } }); }); }); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.testvirtualenvs.ts index 6f6124a6d65e..eb88b2c48d56 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvironmentLocator.testvirtualenvs.ts @@ -10,7 +10,9 @@ suite('GlobalVirtualEnvironment Locator', async () => { const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); const testWorkOnHomePath = path.join(testVirtualHomeDir, 'workonhome'); let workonHomeOldValue: string | undefined; - suiteSetup(async () => { + suiteSetup(async function () { + // https://github.com/microsoft/vscode-python/issues/17798 + return this.skip(); workonHomeOldValue = process.env.WORKON_HOME; process.env.WORKON_HOME = testWorkOnHomePath; }); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/pyenvLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/pyenvLocator.testvirtualenvs.ts index c5deb0d58971..c370a8ff6da5 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/pyenvLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/pyenvLocator.testvirtualenvs.ts @@ -11,7 +11,9 @@ suite('Pyenv Locator', async () => { const testPyenvRoot = path.join(TEST_LAYOUT_ROOT, 'pyenvhome', '.pyenv'); const testPyenvVersionsDir = path.join(testPyenvRoot, 'versions'); let pyenvRootOldValue: string | undefined; - suiteSetup(async () => { + suiteSetup(async function () { + // https://github.com/microsoft/vscode-python/issues/17798 + return this.skip(); pyenvRootOldValue = process.env.PYENV_ROOT; process.env.PYENV_ROOT = testPyenvRoot; }); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/watcherTestUtils.ts b/src/test/pythonEnvironments/base/locators/lowLevel/watcherTestUtils.ts index 274243799141..8130ec281984 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/watcherTestUtils.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/watcherTestUtils.ts @@ -4,8 +4,6 @@ import { assert } from 'chai'; import * as fs from 'fs-extra'; import * as path from 'path'; -import * as sinon from 'sinon'; -import { DiscoveryVariants } from '../../../../../client/common/experiments/groups'; import { traceWarning } from '../../../../../client/common/logger'; import { FileChangeType } from '../../../../../client/common/platform/fileSystemWatcher'; import { createDeferred, Deferred, sleep } from '../../../../../client/common/utils/async'; @@ -138,7 +136,6 @@ export function testLocatorWatcher( }, ): void { let locator: ILocator & IDisposable; - let inExperimentStub: sinon.SinonStub; const venvs = new Venvs(root); async function waitForChangeToBeDetected(deferred: Deferred) { @@ -157,12 +154,6 @@ export function testLocatorWatcher( suiteSetup(async () => { await venvs.cleanUp(); }); - - setup(() => { - inExperimentStub = sinon.stub(externalDeps, 'inExperiment'); - inExperimentStub.withArgs(DiscoveryVariants.discoverWithFileWatching).resolves(true); - }); - async function setupLocator(onChanged: (e: PythonEnvsChangedEvent) => Promise) { locator = options?.arg ? await createLocatorFactoryFunc(options.arg) : await createLocatorFactoryFunc(); locator.onChanged(onChanged); @@ -172,7 +163,6 @@ export function testLocatorWatcher( } teardown(async () => { - sinon.restore(); if (locator) { await locator.dispose(); } diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/windowsStoreLocator.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/windowsStoreLocator.test.ts index a198949dd8f0..02301f862cbe 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/windowsStoreLocator.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/windowsStoreLocator.test.ts @@ -4,9 +4,7 @@ import { assert } from 'chai'; import * as fs from 'fs-extra'; import * as path from 'path'; -import * as sinon from 'sinon'; import { Uri } from 'vscode'; -import { DiscoveryVariants } from '../../../../../client/common/experiments/groups'; import { traceWarning } from '../../../../../client/common/logger'; import { FileChangeType } from '../../../../../client/common/platform/fileSystemWatcher'; import { createDeferred, Deferred, sleep } from '../../../../../client/common/utils/async'; @@ -77,7 +75,6 @@ class WindowsStoreEnvs { } suite('Windows Store Locator', async () => { - let inExperimentStub: sinon.SinonStub; const testLocalAppData = path.join(TEST_LAYOUT_ROOT, 'storeApps'); const testStoreAppRoot = path.join(testLocalAppData, 'Microsoft', 'WindowsApps'); const windowsStoreEnvs = new WindowsStoreEnvs(testStoreAppRoot); @@ -98,16 +95,13 @@ suite('Windows Store Locator', async () => { return items.some((item) => externalDeps.arePathsSame(item.executablePath, executable)); } - suiteSetup(async () => { + suiteSetup(async function () { + // Enable once this is done: https://github.com/microsoft/vscode-python/issues/17797 + return this.skip(); process.env.LOCALAPPDATA = testLocalAppData; await windowsStoreEnvs.cleanUp(); }); - setup(() => { - inExperimentStub = sinon.stub(externalDeps, 'inExperiment'); - inExperimentStub.withArgs(DiscoveryVariants.discoverWithFileWatching).resolves(true); - }); - async function setupLocator(onChanged: (e: PythonEnvsChangedEvent) => Promise) { locator = new WindowsStoreLocator(); await getEnvs(locator.iterEnvs()); // Force the watchers to start. @@ -117,7 +111,6 @@ suite('Windows Store Locator', async () => { } teardown(async () => { - sinon.restore(); await windowsStoreEnvs.cleanUp(); await locator.dispose(); }); diff --git a/src/test/pythonEnvironments/discovery/index.unit.test.ts b/src/test/pythonEnvironments/discovery/index.unit.test.ts deleted file mode 100644 index 33220ffb9b88..000000000000 --- a/src/test/pythonEnvironments/discovery/index.unit.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { SemVer } from 'semver'; -import * as TypeMoq from 'typemoq'; -import { Uri } from 'vscode'; -import { IPlatformService } from '../../../client/common/platform/types'; -import { IDisposableRegistry } from '../../../client/common/types'; -import { getNamesAndValues } from '../../../client/common/utils/enum'; -import { Architecture, OSType } from '../../../client/common/utils/platform'; -import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GLOBAL_VIRTUAL_ENV_SERVICE, - IInterpreterLocatorHelper, - IInterpreterLocatorService, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { PythonInterpreterLocatorService } from '../../../client/pythonEnvironments/discovery/locators'; -import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; - -suite('Interpreters - Locators Index', () => { - let serviceContainer: TypeMoq.IMock; - let platformSvc: TypeMoq.IMock; - let helper: TypeMoq.IMock; - let locator: IInterpreterLocatorService; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - platformSvc = TypeMoq.Mock.ofType(); - helper = 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(IInterpreterLocatorHelper))) - .returns(() => helper.object); - - locator = new PythonInterpreterLocatorService(serviceContainer.object); - }); - [undefined, Uri.file('Something')].forEach((resource) => { - getNamesAndValues(OSType).forEach((osType) => { - if (osType.value === OSType.Unknown) { - return; - } - const testSuffix = `(on ${osType.name}, with${resource ? '' : 'out'} a resource)`; - test(`All Interpreter Sources are used ${testSuffix}`, async () => { - const locatorsTypes: string[] = []; - if (osType.value === OSType.Windows) { - locatorsTypes.push(WINDOWS_REGISTRY_SERVICE); - } - platformSvc.setup((p) => p.osType).returns(() => osType.value); - platformSvc.setup((p) => p.isWindows).returns(() => osType.value === OSType.Windows); - platformSvc.setup((p) => p.isLinux).returns(() => osType.value === OSType.Linux); - platformSvc.setup((p) => p.isMac).returns(() => osType.value === OSType.OSX); - - locatorsTypes.push(CONDA_ENV_SERVICE); - locatorsTypes.push(CONDA_ENV_FILE_SERVICE); - locatorsTypes.push(PIPENV_SERVICE); - locatorsTypes.push(GLOBAL_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(WORKSPACE_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(KNOWN_PATH_SERVICE); - locatorsTypes.push(CURRENT_PATH_SERVICE); - - const locatorsWithInterpreters = locatorsTypes.map((typeName) => { - const interpreter: PythonEnvironment = { - architecture: Architecture.Unknown, - displayName: typeName, - path: typeName, - sysPrefix: typeName, - sysVersion: typeName, - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - }; - - const typeLocator = TypeMoq.Mock.ofType(); - typeLocator - .setup((l) => l.hasInterpreters) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - typeLocator - .setup((l) => l.getInterpreters(TypeMoq.It.isValue(resource))) - .returns(() => Promise.resolve([interpreter])) - .verifiable(TypeMoq.Times.once()); - - serviceContainer - .setup((c) => - c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(typeName)), - ) - .returns(() => typeLocator.object); - - return { - type: typeName, - locator: typeLocator, - interpreters: [interpreter], - }; - }); - - helper - .setup((h) => h.mergeInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(locatorsWithInterpreters.map((item) => item.interpreters[0]))) - .verifiable(TypeMoq.Times.once()); - - await locator.getInterpreters(resource); - - locatorsWithInterpreters.forEach((item) => item.locator.verifyAll()); - helper.verifyAll(); - }); - test(`Interpreter Sources are sorted correctly and merged ${testSuffix}`, async () => { - const locatorsTypes: string[] = []; - if (osType.value === OSType.Windows) { - locatorsTypes.push(WINDOWS_REGISTRY_SERVICE); - } - platformSvc.setup((p) => p.osType).returns(() => osType.value); - platformSvc.setup((p) => p.isWindows).returns(() => osType.value === OSType.Windows); - platformSvc.setup((p) => p.isLinux).returns(() => osType.value === OSType.Linux); - platformSvc.setup((p) => p.isMac).returns(() => osType.value === OSType.OSX); - - locatorsTypes.push(CONDA_ENV_SERVICE); - locatorsTypes.push(CONDA_ENV_FILE_SERVICE); - locatorsTypes.push(PIPENV_SERVICE); - locatorsTypes.push(GLOBAL_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(WORKSPACE_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(KNOWN_PATH_SERVICE); - locatorsTypes.push(CURRENT_PATH_SERVICE); - - const locatorsWithInterpreters = locatorsTypes.map((typeName) => { - const interpreter: PythonEnvironment = { - architecture: Architecture.Unknown, - displayName: typeName, - path: typeName, - sysPrefix: typeName, - sysVersion: typeName, - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - }; - - const typeLocator = TypeMoq.Mock.ofType(); - typeLocator - .setup((l) => l.hasInterpreters) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - typeLocator - .setup((l) => l.getInterpreters(TypeMoq.It.isValue(resource))) - .returns(() => Promise.resolve([interpreter])) - .verifiable(TypeMoq.Times.once()); - - serviceContainer - .setup((c) => - c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(typeName)), - ) - .returns(() => typeLocator.object); - - return { - type: typeName, - locator: typeLocator, - interpreters: [interpreter], - }; - }); - - const expectedInterpreters = locatorsWithInterpreters.map((item) => item.interpreters[0]); - - helper - .setup((h) => h.mergeInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(expectedInterpreters)) - .verifiable(TypeMoq.Times.once()); - - const interpreters = await locator.getInterpreters(resource); - - locatorsWithInterpreters.forEach((item) => item.locator.verifyAll()); - helper.verifyAll(); - expect(interpreters).to.be.deep.equal(expectedInterpreters); - }); - test(`didTriggerInterpreterSuggestions is set to true in the locators if onSuggestion is true ${testSuffix}`, async () => { - const locatorsTypes: string[] = []; - if (osType.value === OSType.Windows) { - locatorsTypes.push(WINDOWS_REGISTRY_SERVICE); - } - platformSvc.setup((p) => p.osType).returns(() => osType.value); - platformSvc.setup((p) => p.isWindows).returns(() => osType.value === OSType.Windows); - platformSvc.setup((p) => p.isLinux).returns(() => osType.value === OSType.Linux); - platformSvc.setup((p) => p.isMac).returns(() => osType.value === OSType.OSX); - - locatorsTypes.push(CONDA_ENV_SERVICE); - locatorsTypes.push(CONDA_ENV_FILE_SERVICE); - locatorsTypes.push(PIPENV_SERVICE); - locatorsTypes.push(GLOBAL_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(WORKSPACE_VIRTUAL_ENV_SERVICE); - locatorsTypes.push(KNOWN_PATH_SERVICE); - locatorsTypes.push(CURRENT_PATH_SERVICE); - - const locatorsWithInterpreters = locatorsTypes.map((typeName) => { - const interpreter: PythonEnvironment = { - architecture: Architecture.Unknown, - displayName: typeName, - path: typeName, - sysPrefix: typeName, - sysVersion: typeName, - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - }; - - const typeLocator = TypeMoq.Mock.ofType(); - typeLocator - .setup((l) => l.hasInterpreters) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - typeLocator - .setup((l) => l.getInterpreters(TypeMoq.It.isValue(resource))) - .returns(() => Promise.resolve([interpreter])) - .verifiable(TypeMoq.Times.once()); - - serviceContainer - .setup((c) => - c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(typeName)), - ) - .returns(() => typeLocator.object); - - return { - type: typeName, - locator: typeLocator, - interpreters: [interpreter], - }; - }); - - helper - .setup((h) => h.mergeInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(locatorsWithInterpreters.map((item) => item.interpreters[0]))); - - await locator.getInterpreters(resource, { onSuggestion: true }); - - locatorsWithInterpreters.forEach((item) => - item.locator.verify((l) => { - l.didTriggerInterpreterSuggestions = true; - }, TypeMoq.Times.once()), - ); - expect(locator.didTriggerInterpreterSuggestions).to.equal( - true, - 'didTriggerInterpreterSuggestions should be set to true.', - ); - }); - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/cacheableLocatorService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/cacheableLocatorService.unit.test.ts deleted file mode 100644 index ed6597c849b9..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/cacheableLocatorService.unit.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -/* eslint-disable max-classes-per-file */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as md5 from 'md5'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; -import { Disposable, Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../client/common/application/types'; -import { WorkspaceService } from '../../../../client/common/application/workspace'; -import { Resource } from '../../../../client/common/types'; -import { noop } from '../../../../client/common/utils/misc'; -import { IInterpreterWatcher } from '../../../../client/interpreter/contracts'; -import { ServiceContainer } from '../../../../client/ioc/container'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { CacheableLocatorService } from '../../../../client/pythonEnvironments/discovery/locators/services/cacheableLocatorService'; -import { PythonEnvironment } from '../../../../client/pythonEnvironments/info'; - -suite('Interpreters - Cacheable Locator Service', () => { - suite('Caching', () => { - class Locator extends CacheableLocatorService { - constructor(name: string, serviceCcontainer: IServiceContainer, private readonly mockLocator: MockLocator) { - super(name, serviceCcontainer); - } - - public dispose() { - noop(); - } - - protected async getInterpretersImplementation(_resource?: Uri): Promise { - return this.mockLocator.getInterpretersImplementation(); - } - - protected getCachedInterpreters(_resource?: Uri): PythonEnvironment[] | undefined { - return this.mockLocator.getCachedInterpreters(); - } - - protected async cacheInterpreters(_interpreters: PythonEnvironment[], _resource?: Uri) { - return this.mockLocator.cacheInterpreters(); - } - - protected getCacheKey(_resource?: Uri) { - return this.mockLocator.getCacheKey(); - } - } - class MockLocator { - // eslint-disable-next-line class-methods-use-this - public async getInterpretersImplementation(): Promise { - return []; - } - - // eslint-disable-next-line class-methods-use-this - public getCachedInterpreters(): PythonEnvironment[] | undefined { - return undefined; - } - - // eslint-disable-next-line class-methods-use-this - public async cacheInterpreters() { - return undefined; - } - - // eslint-disable-next-line class-methods-use-this - public getCacheKey(): string { - return ''; - } - } - let serviceContainer: ServiceContainer; - setup(() => { - serviceContainer = mock(ServiceContainer); - }); - - test('Interpreters must be retrieved once, then cached', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const expectedInterpreters = [1, 2] as any; - const mockedLocatorForVerification = mock(MockLocator); - const locator = new (class extends Locator { - // eslint-disable-next-line class-methods-use-this - protected async addHandlersForInterpreterWatchers( - _cacheKey: string, - _resource: Resource, - ): Promise { - noop(); - } - })('dummy', instance(serviceContainer), instance(mockedLocatorForVerification)); - - when(mockedLocatorForVerification.getInterpretersImplementation()).thenResolve(expectedInterpreters); - when(mockedLocatorForVerification.getCacheKey()).thenReturn('xyz'); - when(mockedLocatorForVerification.getCachedInterpreters()).thenResolve(); - - const [items1, items2, items3] = await Promise.all([ - locator.getInterpreters(), - locator.getInterpreters(), - locator.getInterpreters(), - ]); - expect(items1).to.be.deep.equal(expectedInterpreters); - expect(items2).to.be.deep.equal(expectedInterpreters); - expect(items3).to.be.deep.equal(expectedInterpreters); - - verify(mockedLocatorForVerification.getInterpretersImplementation()).once(); - verify(mockedLocatorForVerification.getCachedInterpreters()).atLeast(1); - verify(mockedLocatorForVerification.cacheInterpreters()).atLeast(1); - }); - - test('Ensure onDidCreate event handler is attached', async () => { - const mockedLocatorForVerification = mock(MockLocator); - class Watcher implements IInterpreterWatcher { - // eslint-disable-next-line class-methods-use-this - public onDidCreate( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _listener: (e: Resource) => any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _thisArgs?: any, - _disposables?: Disposable[], - ): Disposable { - return { dispose: noop }; - } - } - const watcher: IInterpreterWatcher = mock(Watcher); - - const locator = new (class extends Locator { - // eslint-disable-next-line class-methods-use-this - protected async getInterpreterWatchers(_resource: Resource): Promise { - return [instance(watcher)]; - } - })('dummy', instance(serviceContainer), instance(mockedLocatorForVerification)); - - await locator.getInterpreters(); - - verify(watcher.onDidCreate(anything(), anything(), anything())).once(); - }); - - test('Ensure cache is cleared when watcher event fires', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const expectedInterpreters = [1, 2] as any; - const mockedLocatorForVerification = mock(MockLocator); - class Watcher implements IInterpreterWatcher { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private listner?: (e: Resource) => any; - - public onDidCreate( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - listener: (e: Resource) => any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _thisArgs?: any, - _disposables?: Disposable[], - ): Disposable { - this.listner = listener; - return { dispose: noop }; - } - - public invokeListeners() { - this.listner!(undefined); - } - } - const watcher = new Watcher(); - - const locator = new (class extends Locator { - // eslint-disable-next-line class-methods-use-this - protected async getInterpreterWatchers(_resource: Resource): Promise { - return [watcher]; - } - })('dummy', instance(serviceContainer), instance(mockedLocatorForVerification)); - - when(mockedLocatorForVerification.getInterpretersImplementation()).thenResolve(expectedInterpreters); - when(mockedLocatorForVerification.getCacheKey()).thenReturn('xyz'); - when(mockedLocatorForVerification.getCachedInterpreters()).thenResolve(); - - const [items1, items2, items3] = await Promise.all([ - locator.getInterpreters(), - locator.getInterpreters(), - locator.getInterpreters(), - ]); - expect(items1).to.be.deep.equal(expectedInterpreters); - expect(items2).to.be.deep.equal(expectedInterpreters); - expect(items3).to.be.deep.equal(expectedInterpreters); - - verify(mockedLocatorForVerification.getInterpretersImplementation()).once(); - verify(mockedLocatorForVerification.getCachedInterpreters()).atLeast(1); - verify(mockedLocatorForVerification.cacheInterpreters()).once(); - - watcher.invokeListeners(); - - const [items4, items5, items6] = await Promise.all([ - locator.getInterpreters(), - locator.getInterpreters(), - locator.getInterpreters(), - ]); - expect(items4).to.be.deep.equal(expectedInterpreters); - expect(items5).to.be.deep.equal(expectedInterpreters); - expect(items6).to.be.deep.equal(expectedInterpreters); - - // We must get the list of interperters again and cache the new result again. - verify(mockedLocatorForVerification.getInterpretersImplementation()).twice(); - verify(mockedLocatorForVerification.cacheInterpreters()).twice(); - }); - test('Ensure locating event is raised', async () => { - const mockedLocatorForVerification = mock(MockLocator); - const locator = new (class extends Locator { - // eslint-disable-next-line class-methods-use-this - protected async getInterpreterWatchers(_resource: Resource): Promise { - return []; - } - })('dummy', instance(serviceContainer), instance(mockedLocatorForVerification)); - - let locatingEventRaised = false; - locator.onLocating(() => { - locatingEventRaised = true; - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - when(mockedLocatorForVerification.getInterpretersImplementation()).thenResolve([1, 2] as any); - when(mockedLocatorForVerification.getCacheKey()).thenReturn('xyz'); - when(mockedLocatorForVerification.getCachedInterpreters()).thenResolve(); - - await locator.getInterpreters(); - expect(locatingEventRaised).to.be.equal(true, 'Locating Event not raised'); - }); - }); - suite('Cache Key', () => { - class Locator extends CacheableLocatorService { - public dispose() { - noop(); - } - - public getCacheKey(resource?: Uri) { - return super.getCacheKey(resource); - } - - // eslint-disable-next-line class-methods-use-this - protected async getInterpretersImplementation(_resource?: Uri): Promise { - return []; - } - - // eslint-disable-next-line class-methods-use-this - protected getCachedInterpreters(_resource?: Uri): PythonEnvironment[] | undefined { - return []; - } - - // eslint-disable-next-line class-methods-use-this - protected async cacheInterpreters(_interpreters: PythonEnvironment[], _resource?: Uri) { - noop(); - } - } - let serviceContainer: ServiceContainer; - setup(() => { - serviceContainer = mock(ServiceContainer); - }); - - test('Cache Key must contain name of locator', async () => { - const locator = new Locator('hello-World', instance(serviceContainer)); - - const key = locator.getCacheKey(); - - expect(key).contains('hello-World'); - }); - - test('Cache Key must not contain path to workspace', async () => { - const workspace = mock(WorkspaceService); - const workspaceFolder: WorkspaceFolder = { name: '1', index: 1, uri: Uri.file(__dirname) }; - - when(workspace.hasWorkspaceFolders).thenReturn(true); - when(workspace.workspaceFolders).thenReturn([workspaceFolder]); - when(workspace.getWorkspaceFolder(anything())).thenReturn(workspaceFolder); - when(serviceContainer.get(IWorkspaceService)).thenReturn(instance(workspace)); - when(serviceContainer.get(IWorkspaceService, anything())).thenReturn( - instance(workspace), - ); - - const locator = new Locator('hello-World', instance(serviceContainer), false); - - const key = locator.getCacheKey(Uri.file('something')); - - expect(key).contains('hello-World'); - expect(key).not.contains(md5(workspaceFolder.uri.fsPath)); - }); - - test('Cache Key must contain path to workspace', async () => { - const workspace = mock(WorkspaceService); - const workspaceFolder: WorkspaceFolder = { name: '1', index: 1, uri: Uri.file(__dirname) }; - const resource = Uri.file('a'); - - when(workspace.hasWorkspaceFolders).thenReturn(true); - when(workspace.workspaceFolders).thenReturn([workspaceFolder]); - when(workspace.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when(serviceContainer.get(IWorkspaceService)).thenReturn(instance(workspace)); - when(serviceContainer.get(IWorkspaceService, anything())).thenReturn( - instance(workspace), - ); - - const locator = new Locator('hello-World', instance(serviceContainer), true); - - const key = locator.getCacheKey(resource); - - expect(key).contains('hello-World'); - expect(key).contains(md5(workspaceFolder.uri.fsPath)); - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/condaEnvFileService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaEnvFileService.unit.test.ts deleted file mode 100644 index edda32cf2c2f..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/condaEnvFileService.unit.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { IFileSystem } from '../../../../client/common/platform/types'; -import { IPersistentStateFactory } from '../../../../client/common/types'; -import { - ICondaLocatorService, - IInterpreterHelper, - IInterpreterLocatorService, -} from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { AnacondaCompanyName } from '../../../../client/pythonEnvironments/common/environmentManagers/conda'; -import { CondaEnvFileService } from '../../../../client/pythonEnvironments/discovery/locators/services/condaEnvFileService'; -import { EnvironmentType } from '../../../../client/pythonEnvironments/info'; -import { MockState } from '../../../interpreters/mocks'; - -const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -const environmentsFilePath = path.join(environmentsPath, 'environments.txt'); - -suite('Interpreters from Conda Environments Text File', () => { - let condaService: TypeMoq.IMock; - let interpreterHelper: TypeMoq.IMock; - let condaFileProvider: IInterpreterLocatorService; - let fileSystem: TypeMoq.IMock; - setup(() => { - 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(); - interpreterHelper = TypeMoq.Mock.ofType(); - fileSystem = TypeMoq.Mock.ofType(); - condaFileProvider = new CondaEnvFileService( - interpreterHelper.object, - condaService.object, - fileSystem.object, - serviceContainer.object, - ); - }); - test('Must return an empty list if environment file cannot be found', async () => { - condaService.setup((c) => c.condaEnvironmentsFile).returns(() => undefined); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - const interpreters = await condaFileProvider.getInterpreters(); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return an empty list for an empty file', async () => { - condaService.setup((c) => c.condaEnvironmentsFile).returns(() => environmentsFilePath); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))) - .returns(() => Promise.resolve(true)); - fileSystem - .setup((fs) => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))) - .returns(() => Promise.resolve('')); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - const interpreters = await condaFileProvider.getInterpreters(); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - - async function filterFilesInEnvironmentsFileAndReturnValidItems(isWindows: boolean) { - const validPaths = [ - path.join(environmentsPath, 'conda', 'envs', 'numpy'), - path.join(environmentsPath, 'conda', 'envs', 'scipy'), - ]; - const interpreterPaths = [ - path.join(environmentsPath, 'xyz', 'one'), - path.join(environmentsPath, 'xyz', 'two'), - path.join(environmentsPath, 'xyz', 'python.exe'), - ].concat(validPaths); - condaService.setup((c) => c.condaEnvironmentsFile).returns(() => environmentsFilePath); - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - condaService - .setup((c) => c.getCondaEnvironments(TypeMoq.It.isAny())) - .returns(() => { - const condaEnvironments = validPaths.map((item) => ({ - path: item, - name: path.basename(item), - })); - return Promise.resolve(condaEnvironments); - }); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))) - .returns(() => Promise.resolve(true)); - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p1: string, p2: string) => (isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase())); - validPaths.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - - fileSystem - .setup((fs) => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))) - .returns(() => Promise.resolve(interpreterPaths.join(EOL))); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - - const interpreters = await condaFileProvider.getInterpreters(); - - const expectedPythonPath = isWindows - ? path.join(validPaths[0], 'python.exe') - : path.join(validPaths[0], 'bin', 'python'); - assert.equal(interpreters.length, 2, 'Incorrect number of entries'); - assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect display name'); - assert.equal(interpreters[0].path, expectedPythonPath, 'Incorrect path'); - assert.equal(interpreters[0].envPath, validPaths[0], 'Incorrect envpath'); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Incorrect type'); - } - test('Must filter files in the list and return valid items (non windows)', async () => { - await filterFilesInEnvironmentsFileAndReturnValidItems(false); - }); - test('Must filter files in the list and return valid items (windows)', async () => { - await filterFilesInEnvironmentsFileAndReturnValidItems(true); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/condaEnvService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaEnvService.unit.test.ts deleted file mode 100644 index 4e530bed2aab..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/condaEnvService.unit.test.ts +++ /dev/null @@ -1,482 +0,0 @@ -import * as assert from 'assert'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { IFileSystem } from '../../../../client/common/platform/types'; -import { IPersistentStateFactory } from '../../../../client/common/types'; -import { ICondaLocatorService, IInterpreterHelper } from '../../../../client/interpreter/contracts'; -import { InterpreterHelper } from '../../../../client/interpreter/helpers'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { - AnacondaCompanyName, - CondaInfo, - parseCondaInfo, -} from '../../../../client/pythonEnvironments/common/environmentManagers/conda'; -import { CondaEnvService } from '../../../../client/pythonEnvironments/discovery/locators/services/condaEnvService'; -import { EnvironmentType } from '../../../../client/pythonEnvironments/info'; -import { MockState } from '../../../interpreters/mocks'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); - -suite('Interpreters from Conda Environments', () => { - let ioc: UnitTestIocContainer; - let condaProvider: CondaEnvService; - let condaService: TypeMoq.IMock; - let interpreterHelper: TypeMoq.IMock; - let fileSystem: TypeMoq.IMock; - setup(async () => { - await 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(); - interpreterHelper = TypeMoq.Mock.ofType(); - fileSystem = TypeMoq.Mock.ofType(); - condaProvider = new CondaEnvService( - condaService.object, - interpreterHelper.object, - serviceContainer.object, - fileSystem.object, - ); - }); - teardown(() => ioc.dispose()); - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - function _parseCondaInfo(info: CondaInfo, conda: ICondaLocatorService, fs: IFileSystem, h: IInterpreterHelper) { - return parseCondaInfo(info, conda.getInterpreterPath, fs.fileExists, h.getInterpreterInformation); - } - - test('Must return an empty list for empty json', async () => { - const interpreters = await _parseCondaInfo( - {} as CondaInfo, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - - async function extractDisplayNameFromVersionInfo(isWindows: boolean) { - const info = { - envs: [ - path.join(environmentsPath, 'conda', 'envs', 'numpy'), - path.join(environmentsPath, 'conda', 'envs', 'scipy'), - ], - default_prefix: '', - 'sys.version': - '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]', - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - - const interpreters = await _parseCondaInfo( - info, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - assert.equal(interpreters.length, 2, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - - const path2 = path.join(info.envs[1], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[1].path, path2, 'Incorrect path for first env'); - assert.equal( - interpreters[1].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[1].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must extract display name from version info (non windows)', async () => { - await extractDisplayNameFromVersionInfo(false); - }); - test('Must extract display name from version info (windows)', async () => { - await extractDisplayNameFromVersionInfo(true); - }); - async function extractDisplayNameFromVersionInfoSuffixedWithEnvironmentName(isWindows: boolean) { - const info = { - envs: [ - path.join(environmentsPath, 'conda', 'envs', 'numpy'), - path.join(environmentsPath, 'conda', 'envs', 'scipy'), - ], - default_prefix: path.join(environmentsPath, 'conda', 'envs', 'root'), - 'sys.version': - '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]', - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup((c) => c.getCondaInfo()).returns(() => Promise.resolve(info)); - condaService - .setup((c) => c.getCondaEnvironments(TypeMoq.It.isAny())) - .returns(() => - Promise.resolve([ - { name: 'base', path: environmentsPath }, - { name: 'numpy', path: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { name: 'scipy', path: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - ]), - ); - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p1: string, p2: string) => (isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase())); - - const interpreters = await condaProvider.getInterpreters(); - assert.equal(interpreters.length, 2, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - - const path2 = path.join(info.envs[1], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[1].path, path2, 'Incorrect path for first env'); - assert.equal( - interpreters[1].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[1].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must extract display name from version info suffixed with the environment name (oxs/linux)', async () => { - await extractDisplayNameFromVersionInfoSuffixedWithEnvironmentName(false); - }); - test('Must extract display name from version info suffixed with the environment name (windows)', async () => { - await extractDisplayNameFromVersionInfoSuffixedWithEnvironmentName(true); - }); - - async function useDefaultNameIfSysVersionIsInvalid(isWindows: boolean) { - const info = { - envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')], - default_prefix: '', - 'sys.version': - '3.6.1 |Anaonda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]', - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - - const interpreters = await _parseCondaInfo( - info, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must use the default display name if sys.version is invalid (non windows)', async () => { - await useDefaultNameIfSysVersionIsInvalid(false); - }); - test('Must use the default display name if sys.version is invalid (windows)', async () => { - await useDefaultNameIfSysVersionIsInvalid(true); - }); - - async function useDefaultNameIfSysVersionIsValidAndSuffixWithEnvironmentName(isWindows: boolean) { - const info = { - envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')], - default_prefix: '', - 'sys.version': - '3.6.1 |Anaonda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]', - }; - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - condaService.setup((c) => c.getCondaInfo()).returns(() => Promise.resolve(info)); - condaService - .setup((c) => c.getCondaEnvironments(TypeMoq.It.isAny())) - .returns(() => - Promise.resolve([ - { name: 'base', path: environmentsPath }, - { name: 'numpy', path: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { name: 'scipy', path: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - ]), - ); - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined)); - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p1: string, p2: string) => (isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase())); - - const interpreters = await condaProvider.getInterpreters(); - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must use the default display name if sys.version is invalid and suffixed with environment name (non windows)', async () => { - await useDefaultNameIfSysVersionIsValidAndSuffixWithEnvironmentName(false); - }); - test('Must use the default display name if sys.version is invalid and suffixed with environment name (windows)', async () => { - await useDefaultNameIfSysVersionIsValidAndSuffixWithEnvironmentName(false); - }); - - async function useDefaultNameIfSysVersionIsEmpty(isWindows: boolean) { - const info = { - envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')], - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - - const interpreters = await _parseCondaInfo( - info, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - - test('Must use the default display name if sys.version is empty (non windows)', async () => { - await useDefaultNameIfSysVersionIsEmpty(false); - }); - test('Must use the default display name if sys.version is empty (windows)', async () => { - await useDefaultNameIfSysVersionIsEmpty(true); - }); - - async function useDefaultNameIfSysVersionIsEmptyAndSuffixWithEnvironmentName(isWindows: boolean) { - const info = { - envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')], - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - info.envs.forEach((validPath) => { - const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup((c) => c.getCondaInfo()).returns(() => Promise.resolve(info)); - condaService - .setup((c) => c.getCondaEnvironments(TypeMoq.It.isAny())) - .returns(() => - Promise.resolve([ - { name: 'base', path: environmentsPath }, - { name: 'numpy', path: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { name: 'scipy', path: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - ]), - ); - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p1: string, p2: string) => (isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase())); - - const interpreters = await condaProvider.getInterpreters(); - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - - const path1 = path.join(info.envs[0], isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must use the default display name if sys.version is empty and suffixed with environment name (non windows)', async () => { - await useDefaultNameIfSysVersionIsEmptyAndSuffixWithEnvironmentName(false); - }); - test('Must use the default display name if sys.version is empty and suffixed with environment name (windows)', async () => { - await useDefaultNameIfSysVersionIsEmptyAndSuffixWithEnvironmentName(true); - }); - - async function includeDefaultPrefixIntoListOfInterpreters(isWindows: boolean) { - const info = { - default_prefix: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }; - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isAny())) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - const pythonPath = isWindows - ? path.join(info.default_prefix, 'python.exe') - : path.join(info.default_prefix, 'bin', 'python'); - fileSystem.setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - - const interpreters = await _parseCondaInfo( - info, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - - const path1 = path.join(info.default_prefix, isWindows ? 'python.exe' : path.join('bin', 'python')); - assert.equal(interpreters[0].path, path1, 'Incorrect path for first env'); - assert.equal( - interpreters[0].companyDisplayName, - AnacondaCompanyName, - 'Incorrect company display name for first env', - ); - assert.equal(interpreters[0].envType, EnvironmentType.Conda, 'Environment not detected as a conda environment'); - } - test('Must include the default_prefix into the list of interpreters (non windows)', async () => { - await includeDefaultPrefixIntoListOfInterpreters(false); - }); - test('Must include the default_prefix into the list of interpreters (windows)', async () => { - await includeDefaultPrefixIntoListOfInterpreters(true); - }); - - async function excludeInterpretersThatDoNotExistOnFileSystem(isWindows: boolean) { - const info = { - envs: [ - path.join(environmentsPath, 'conda', 'envs', 'numpy'), - path.join(environmentsPath, 'path0', 'one.exe'), - path.join(environmentsPath, 'path1', 'one.exe'), - path.join(environmentsPath, 'path2', 'one.exe'), - path.join(environmentsPath, 'conda', 'envs', 'scipy'), - path.join(environmentsPath, 'path3', 'three.exe'), - ], - }; - const validPaths = info.envs.filter((_, index) => index % 2 === 0); - interpreterHelper - .setup((i) => i.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: undefined })); - validPaths.forEach((envPath) => { - condaService - .setup((c) => c.getInterpreterPath(TypeMoq.It.isValue(envPath))) - .returns((environmentPath) => - isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'), - ); - const pythonPath = isWindows ? path.join(envPath, 'python.exe') : path.join(envPath, 'bin', 'python'); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)); - }); - - const interpreters = await _parseCondaInfo( - info, - condaService.object, - fileSystem.object, - interpreterHelper.object, - ); - - assert.equal(interpreters.length, validPaths.length, 'Incorrect number of entries'); - validPaths.forEach((envPath, index) => { - assert.equal(interpreters[index].envPath!, envPath, 'Incorrect env path'); - const pythonPath = isWindows ? path.join(envPath, 'python.exe') : path.join(envPath, 'bin', 'python'); - assert.equal(interpreters[index].path, pythonPath, 'Incorrect python Path'); - }); - } - - test('Must exclude interpreters that do not exist on disc (non windows)', async () => { - await excludeInterpretersThatDoNotExistOnFileSystem(false); - }); - test('Must exclude interpreters that do not exist on disc (windows)', async () => { - await excludeInterpretersThatDoNotExistOnFileSystem(true); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts deleted file mode 100644 index 885ace948252..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect } from 'chai'; -import { parseCondaEnvFileContents } from '../../../../client/pythonEnvironments/discovery/locators/services/condaHelper'; - -suite('Interpreters display name from Conda Environments', () => { - test('Parse conda environments', () => { - const environments = ` -# conda environments: -# -base * /Users/donjayamanne/anaconda3 - * /Users/donjayamanne/anaconda3 -one /Users/donjayamanne/anaconda3/envs/one - one /Users/donjayamanne/anaconda3/envs/ one -one two /Users/donjayamanne/anaconda3/envs/one two -three /Users/donjayamanne/anaconda3/envs/three - /Users/donjayamanne/anaconda3/envs/four - /Users/donjayamanne/anaconda3/envs/five six -aaaa_bbbb_cccc_dddd_eeee_ffff_gggg /Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg -aaaa_bbbb_cccc_dddd_eeee_ffff_gggg * /Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg -with*star /Users/donjayamanne/anaconda3/envs/with*star -with*one*two*three*four*five*six*seven* /Users/donjayamanne/anaconda3/envs/with*one*two*three*four*five*six*seven* -with*one*two*three*four*five*six*seven* * /Users/donjayamanne/anaconda3/envs/with*one*two*three*four*five*six*seven* - /Users/donjayamanne/anaconda3/envs/seven `; // note the space after seven - - const expectedList = [ - { name: 'base', path: '/Users/donjayamanne/anaconda3', isActive: true }, - { name: '', path: '/Users/donjayamanne/anaconda3', isActive: true }, - { name: 'one', path: '/Users/donjayamanne/anaconda3/envs/one', isActive: false }, - { name: ' one', path: '/Users/donjayamanne/anaconda3/envs/ one', isActive: false }, - { name: 'one two', path: '/Users/donjayamanne/anaconda3/envs/one two', isActive: false }, - { name: 'three', path: '/Users/donjayamanne/anaconda3/envs/three', isActive: false }, - { name: '', path: '/Users/donjayamanne/anaconda3/envs/four', isActive: false }, - { name: '', path: '/Users/donjayamanne/anaconda3/envs/five six', isActive: false }, - { - name: 'aaaa_bbbb_cccc_dddd_eeee_ffff_gggg', - path: '/Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg', - isActive: false, - }, - { - name: 'aaaa_bbbb_cccc_dddd_eeee_ffff_gggg', - path: '/Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg', - isActive: true, - }, - { name: 'with*star', path: '/Users/donjayamanne/anaconda3/envs/with*star', isActive: false }, - { - name: 'with*one*two*three*four*five*six*seven*', - path: '/Users/donjayamanne/anaconda3/envs/with*one*two*three*four*five*six*seven*', - isActive: false, - }, - { - name: 'with*one*two*three*four*five*six*seven*', - path: '/Users/donjayamanne/anaconda3/envs/with*one*two*three*four*five*six*seven*', - isActive: true, - }, - { name: '', path: '/Users/donjayamanne/anaconda3/envs/seven ', isActive: false }, - ]; - - const list = parseCondaEnvFileContents(environments); - expect(list).deep.equal(expectedList); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/condaLocatorService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaLocatorService.unit.test.ts deleted file mode 100644 index 4cc52b9e2b29..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/condaLocatorService.unit.test.ts +++ /dev/null @@ -1,1068 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as assert from 'assert'; -import { expect } from 'chai'; -import { EOL } from 'os'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import * as TypeMoq from 'typemoq'; -import { Disposable, EventEmitter } from 'vscode'; - -import { IWorkspaceService } from '../../../../client/common/application/types'; -import { FileSystemPaths, FileSystemPathUtils } from '../../../../client/common/platform/fs-paths'; -import { IFileSystem, IPlatformService } from '../../../../client/common/platform/types'; -import { IProcessService, IProcessServiceFactory } from '../../../../client/common/process/types'; -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, - WINDOWS_REGISTRY_SERVICE, -} from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { CondaLocatorService } from '../../../../client/pythonEnvironments/discovery/locators/services/condaLocatorService'; -import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; -import { MockState } from '../../../interpreters/mocks'; - -const untildify: (value: string) => string = require('untildify'); - -const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -const info: PythonEnvironment = { - architecture: Architecture.Unknown, - companyDisplayName: '', - displayName: '', - envName: '', - path: '', - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - sysPrefix: '', - sysVersion: '', -}; - -suite('Interpreters Conda Service', () => { - let processService: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - let condaService: CondaLocatorService; - let fileSystem: TypeMoq.IMock; - let config: TypeMoq.IMock; - let settings: TypeMoq.IMock; - let registryInterpreterLocatorService: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let procServiceFactory: TypeMoq.IMock; - let persistentStateFactory: TypeMoq.IMock; - let condaPathSetting: string; - let disposableRegistry: Disposable[]; - let interpreterService: TypeMoq.IMock; - let workspaceService: TypeMoq.IMock; - let mockState: MockState; - let terminalProvider: TypeMoq.IMock; - setup(async () => { - condaPathSetting = ''; - processService = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - persistentStateFactory = TypeMoq.Mock.ofType(); - interpreterService = TypeMoq.Mock.ofType(); - registryInterpreterLocatorService = TypeMoq.Mock.ofType(); - fileSystem = TypeMoq.Mock.ofType(); - workspaceService = TypeMoq.Mock.ofType(); - config = TypeMoq.Mock.ofType(); - settings = TypeMoq.Mock.ofType(); - procServiceFactory = TypeMoq.Mock.ofType(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((x: any) => x.then).returns(() => undefined); - procServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)); - disposableRegistry = []; - const e = new EventEmitter(); - interpreterService.setup((x) => x.onDidChangeInterpreter).returns(() => e.event); - resetMockState(undefined); - persistentStateFactory - .setup((s) => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => mockState); - - terminalProvider = TypeMoq.Mock.ofType(); - terminalProvider.setup((p) => p.isShellSupported(TypeMoq.It.isAny())).returns(() => true); - terminalProvider - .setup((p) => p.getActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(['activate'])); - terminalProvider - .setup((p) => p.getActivationCommandsForInterpreter!(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(['activate'])); - - serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())) - .returns(() => procServiceFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())) - .returns(() => platformService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())) - .returns(() => fileSystem.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) - .returns(() => config.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ITerminalActivationCommandProvider), TypeMoq.It.isAny())) - .returns(() => terminalProvider.object); - serviceContainer - .setup((c) => - c.get(TypeMoq.It.isValue(IInterpreterLocatorService), TypeMoq.It.isValue(WINDOWS_REGISTRY_SERVICE)), - ) - .returns(() => registryInterpreterLocatorService.object); - serviceContainer - .setup((c) => c.getAll(TypeMoq.It.isValue(ITerminalActivationCommandProvider), TypeMoq.It.isAny())) - .returns(() => [terminalProvider.object]); - config.setup((c) => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => settings.object); - settings.setup((p) => p.condaPath).returns(() => condaPathSetting); - fileSystem - .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p1, p2) => { - const utils = FileSystemPathUtils.withDefaults( - FileSystemPaths.withDefaults(platformService.object.isWindows), - ); - return utils.arePathsSame(p1, p2); - }); - - condaService = new CondaLocatorService( - procServiceFactory.object, - platformService.object, - fileSystem.object, - persistentStateFactory.object, - config.object, - disposableRegistry, - workspaceService.object, - serviceContainer.object, - ); - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function resetMockState(data: any) { - mockState = new MockState(data); - } - - async function identifyPythonPathAsCondaEnvironment( - isWindows: boolean, - isOsx: boolean, - isLinux: boolean, - pythonPath: string, - ) { - platformService.setup((p) => p.isLinux).returns(() => isLinux); - platformService.setup((p) => p.isWindows).returns(() => isWindows); - platformService.setup((p) => p.isMac).returns(() => isOsx); - - const isCondaEnv = await condaService.isCondaEnvironment(pythonPath); - expect(isCondaEnv).to.be.equal(true, 'Path not identified as a conda path'); - } - - test('Correctly identifies a python path as a conda environment (windows)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await identifyPythonPathAsCondaEnvironment(true, false, false, pythonPath); - }); - - test('Correctly identifies a python path as a conda environment (linux)', async () => { - const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await identifyPythonPathAsCondaEnvironment(false, false, true, pythonPath); - }); - - test('Correctly identifies a python path as a conda environment (osx)', async () => { - const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await identifyPythonPathAsCondaEnvironment(false, true, false, pythonPath); - }); - - async function identifyPythonPathAsNonCondaEnvironment( - isWindows: boolean, - isOsx: boolean, - isLinux: boolean, - pythonPath: string, - ) { - platformService.setup((p) => p.isLinux).returns(() => isLinux); - platformService.setup((p) => p.isWindows).returns(() => isWindows); - platformService.setup((p) => p.isMac).returns(() => isOsx); - - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(false)); - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(false)); - - const isCondaEnv = await condaService.isCondaEnvironment(pythonPath); - expect(isCondaEnv).to.be.equal(false, 'Path incorrectly identified as a conda path'); - } - - test('Correctly identifies a python path as a non-conda environment (windows)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); - await identifyPythonPathAsNonCondaEnvironment(true, false, false, pythonPath); - }); - - test('Correctly identifies a python path as a non-conda environment (linux)', async () => { - const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - await identifyPythonPathAsNonCondaEnvironment(false, false, true, pythonPath); - }); - - test('Correctly identifies a python path as a non-conda environment (osx)', async () => { - const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - await identifyPythonPathAsNonCondaEnvironment(false, true, false, pythonPath); - }); - - async function checkCondaNameAndPathForCondaEnvironments( - isWindows: boolean, - isOsx: boolean, - isLinux: boolean, - pythonPath: string, - condaEnvsPath: string, - expectedCondaEnv?: { name: string; path: string }, - ) { - const condaEnvironments = [ - { name: 'One', path: path.join(condaEnvsPath, 'one') }, - { name: 'Three', path: path.join(condaEnvsPath, 'three') }, - { name: 'Seven', path: path.join(condaEnvsPath, 'seven') }, - { name: 'Eight', path: path.join(condaEnvsPath, 'Eight 8') }, - { name: 'nine 9', path: path.join(condaEnvsPath, 'nine 9') }, - ]; - - platformService.setup((p) => p.isLinux).returns(() => isLinux); - platformService.setup((p) => p.isWindows).returns(() => isWindows); - platformService.setup((p) => p.isMac).returns(() => isOsx); - - resetMockState({ data: condaEnvironments }); - - const condaEnv = await condaService.getCondaEnvironment(pythonPath); - expect(condaEnv).deep.equal(expectedCondaEnv, 'Conda environment not identified'); - } - - test('Correctly retrieves conda environment (windows)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python.exe'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(true, false, false, pythonPath, condaEnvDir, { - name: 'One', - path: path.dirname(pythonPath), - }); - }); - - test('Correctly retrieves conda environment with spaces in env name (windows)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'eight 8', 'python.exe'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(true, false, false, pythonPath, condaEnvDir, { - name: 'Eight', - path: path.dirname(pythonPath), - }); - }); - - test('Correctly retrieves conda environment (osx)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'bin', 'python'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(false, true, false, pythonPath, condaEnvDir, { - name: 'One', - path: path.join(path.dirname(pythonPath), '..'), - }); - }); - - test('Correctly retrieves conda environment with spaces in env name (osx)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'Eight 8', 'bin', 'python'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(false, true, false, pythonPath, condaEnvDir, { - name: 'Eight', - path: path.join(path.dirname(pythonPath), '..'), - }); - }); - - test('Correctly retrieves conda environment (linux)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'bin', 'python'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(false, false, true, pythonPath, condaEnvDir, { - name: 'One', - path: path.join(path.dirname(pythonPath), '..'), - }); - }); - - test('Correctly retrieves conda environment with spaces in env name (linux)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'Eight 8', 'bin', 'python'); - const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - fileSystem - .setup((f) => - f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta'))), - ) - .returns(() => Promise.resolve(true)); - await checkCondaNameAndPathForCondaEnvironments(false, false, true, pythonPath, condaEnvDir, { - name: 'Eight', - path: path.join(path.dirname(pythonPath), '..'), - }); - }); - - test('Ignore cache if environment is not found in the cache (conda env is detected second time round)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'newEnvironment', 'python.exe'); - const condaEnvsPath = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - const condaEnvironments = [ - { name: 'One', path: path.join(condaEnvsPath, 'one') }, - { name: 'Three', path: path.join(condaEnvsPath, 'three') }, - { name: 'Seven', path: path.join(condaEnvsPath, 'seven') }, - { name: 'Eight', path: path.join(condaEnvsPath, 'Eight 8') }, - { name: 'nine 9', path: path.join(condaEnvsPath, 'nine 9') }, - ]; - - platformService.setup((p) => p.isLinux).returns(() => false); - platformService.setup((p) => p.isWindows).returns(() => true); - platformService.setup((p) => p.isMac).returns(() => false); - - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - resetMockState({ data: condaEnvironments }); - - const envList = [ - '# conda environments:', - '#', - 'base * /Users/donjayamanne/anaconda3', - 'one /Users/donjayamanne/anaconda3/envs/one', - 'one two /Users/donjayamanne/anaconda3/envs/one two', - 'py27 /Users/donjayamanne/anaconda3/envs/py27', - 'py36 /Users/donjayamanne/anaconda3/envs/py36', - 'three /Users/donjayamanne/anaconda3/envs/three', - `newEnvironment ${path.join(condaEnvsPath, 'newEnvironment')}`, - ]; - - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: envList.join(EOL) })); - - const condaEnv = await condaService.getCondaEnvironment(pythonPath); - expect(condaEnv).deep.equal( - { name: 'newEnvironment', path: path.dirname(pythonPath) }, - 'Conda environment not identified after ignoring cache', - ); - expect(mockState.data.data).lengthOf(7, 'Incorrect number of items in the cache'); - }); - - test('Ignore cache if environment is not found in the cache (conda env is not detected in conda env list)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'newEnvironment', 'python.exe'); - const condaEnvsPath = path.join('c', 'users', 'xyz', '.conda', 'envs'); - - const condaEnvironments = [ - { name: 'One', path: path.join(condaEnvsPath, 'one') }, - { name: 'Three', path: path.join(condaEnvsPath, 'three') }, - { name: 'Seven', path: path.join(condaEnvsPath, 'seven') }, - { name: 'Eight', path: path.join(condaEnvsPath, 'Eight 8') }, - { name: 'nine 9', path: path.join(condaEnvsPath, 'nine 9') }, - ]; - - platformService.setup((p) => p.isLinux).returns(() => false); - platformService.setup((p) => p.isWindows).returns(() => true); - platformService.setup((p) => p.isMac).returns(() => false); - - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - resetMockState({ data: condaEnvironments }); - - const envList = [ - '# conda environments:', - '#', - 'base * /Users/donjayamanne/anaconda3', - 'one /Users/donjayamanne/anaconda3/envs/one', - 'one two /Users/donjayamanne/anaconda3/envs/one two', - 'py27 /Users/donjayamanne/anaconda3/envs/py27', - 'py36 /Users/donjayamanne/anaconda3/envs/py36', - 'three /Users/donjayamanne/anaconda3/envs/three', - ]; - - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: envList.join(EOL) })); - - const condaEnv = await condaService.getCondaEnvironment(pythonPath); - expect(condaEnv).deep.equal(undefined, 'Conda environment incorrectly identified after ignoring cache'); - expect(mockState.data.data).lengthOf(6, 'Incorrect number of items in the cache'); - }); - - test('Must use Conda env from Registry to locate conda.exe', async () => { - const condaPythonExePath = path.join('dumyPath', 'environments', 'conda', 'Scripts', 'python.exe'); - const registryInterpreters: PythonEnvironment[] = [ - { - displayName: 'One', - path: path.join(environmentsPath, 'path1', 'one.exe'), - companyDisplayName: 'One 1', - version: new SemVer('1.0.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: condaPythonExePath, - companyDisplayName: 'Two 2', - version: new SemVer('1.11.0'), - envType: EnvironmentType.Conda, - }, - { - displayName: 'Three', - path: path.join(environmentsPath, 'path2', 'one.exe'), - companyDisplayName: 'Three 3', - version: new SemVer('2.10.1'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Seven', - path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - companyDisplayName: 'Continuum Analytics, Inc.', - envType: EnvironmentType.Unknown, - }, - ].map((item) => ({ ...info, ...item })); - const condaInterpreterIndex = registryInterpreters.findIndex((i) => i.displayName === 'Anaconda'); - const expectedCondaPath = path.join( - path.dirname(registryInterpreters[condaInterpreterIndex].path), - 'conda.exe', - ); - platformService.setup((p) => p.isWindows).returns(() => true); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - registryInterpreterLocatorService - .setup((r) => r.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(registryInterpreters)); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isAny())) - .returns((file: string) => Promise.resolve(file === expectedCondaPath)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, expectedCondaPath, 'Failed to identify conda.exe'); - }); - - test('Must use Conda env from Registry to latest version of locate conda.exe', async () => { - const condaPythonExePath = path.join('dumyPath', 'environments'); - const registryInterpreters: PythonEnvironment[] = [ - { - displayName: 'One', - path: path.join(environmentsPath, 'path1', 'one.exe'), - companyDisplayName: 'One 1', - version: new SemVer('1.0.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 1', - version: new SemVer('1.11.0'), - envType: EnvironmentType.Conda, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.11', - version: new SemVer('2.11.0'), - envType: EnvironmentType.Conda, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.31', - version: new SemVer('2.31.0'), - envType: EnvironmentType.Conda, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.21', - version: new SemVer('2.21.0'), - envType: EnvironmentType.Conda, - }, - { - displayName: 'Three', - path: path.join(environmentsPath, 'path2', 'one.exe'), - companyDisplayName: 'Three 3', - version: new SemVer('2.10.1'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Seven', - path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - companyDisplayName: 'Continuum Analytics, Inc.', - envType: EnvironmentType.Unknown, - }, - ].map((item) => ({ ...info, ...item })); - const indexOfLatestVersion = 3; - const expectedCodnaPath = path.join(path.dirname(registryInterpreters[indexOfLatestVersion].path), 'conda.exe'); - platformService.setup((p) => p.isWindows).returns(() => true); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - registryInterpreterLocatorService - .setup((r) => r.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(registryInterpreters)); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isAny())) - .returns((file: string) => Promise.resolve(file === expectedCodnaPath)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, expectedCodnaPath, 'Failed to identify conda.exe'); - }); - - test("Must use 'conda' if conda.exe cannot be located using registry entries", async () => { - const condaPythonExePath = path.join('dumyPath', 'environments'); - const registryInterpreters: PythonEnvironment[] = [ - { - displayName: 'One', - path: path.join(environmentsPath, 'path1', 'one.exe'), - companyDisplayName: 'One 1', - version: new SemVer('1.0.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda1', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 1', - version: new SemVer('1.11.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda211', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.11', - version: new SemVer('2.11.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda231', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.31', - version: new SemVer('2.31.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), - companyDisplayName: 'Two 2.21', - version: new SemVer('2.21.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Three', - path: path.join(environmentsPath, 'path2', 'one.exe'), - companyDisplayName: 'Three 3', - version: new SemVer('2.10.1'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Seven', - path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - companyDisplayName: 'Continuum Analytics, Inc.', - envType: EnvironmentType.Unknown, - }, - ].map((item) => ({ ...info, ...item })); - platformService.setup((p) => p.isWindows).returns(() => true); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - registryInterpreterLocatorService - .setup((r) => r.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(registryInterpreters)); - fileSystem.setup((fs) => fs.search(TypeMoq.It.isAnyString())).returns(async () => []); - fileSystem.setup((fs) => fs.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, 'conda', 'Failed to identify conda.exe'); - }); - - test('Get conda file from default/known locations', async () => { - const expected = 'C:/ProgramData/Miniconda2/Scripts/conda.exe'; - registryInterpreterLocatorService - .setup((r) => r.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve([])); - - platformService.setup((p) => p.isWindows).returns(() => true); - - fileSystem.setup((f) => f.search(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([expected])); - const CondaLocatorServiceForTesting = class extends CondaLocatorService { - // eslint-disable-next-line class-methods-use-this - public async isCondaInCurrentPath() { - return false; - } - }; - const condaSrv = new CondaLocatorServiceForTesting( - procServiceFactory.object, - platformService.object, - fileSystem.object, - persistentStateFactory.object, - config.object, - disposableRegistry, - workspaceService.object, - serviceContainer.object, - ); - - const result = await condaSrv.getCondaFile(); - expect(result).is.equal(expected); - }); - - test("Must use 'python.condaPath' setting if set", async () => { - condaPathSetting = 'spam-spam-conda-spam-spam'; - // We ensure that conda would otherwise be found. - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']))) - .returns(() => Promise.resolve({ stdout: 'xyz' })) - .verifiable(TypeMoq.Times.never()); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, 'spam-spam-conda-spam-spam', 'Failed to identify conda.exe'); - - // We should not try to call other unwanted methods. - processService.verifyAll(); - registryInterpreterLocatorService.verify((r) => r.getInterpreters(TypeMoq.It.isAny()), TypeMoq.Times.never()); - }); - - test("Must use 'conda' if is available in the current path", async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']))) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, 'conda', 'Failed to identify conda.exe'); - - // We should not try to call other unwanted methods. - registryInterpreterLocatorService.verify((r) => r.getInterpreters(TypeMoq.It.isAny()), TypeMoq.Times.never()); - }); - - test('Must invoke process only once to check if conda is in the current path', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']))) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, 'conda', 'Failed to identify conda.exe'); - processService.verify( - (p) => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - - // We should not try to call other unwanted methods. - registryInterpreterLocatorService.verify((r) => r.getInterpreters(TypeMoq.It.isAny()), TypeMoq.Times.never()); - - await condaService.getCondaFile(); - processService.verify( - (p) => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - TypeMoq.Times.once(), - ); - }); - - [ - '~/anaconda/bin/conda', - '~/miniconda/bin/conda', - '~/anaconda2/bin/conda', - '~/miniconda2/bin/conda', - '~/anaconda3/bin/conda', - '~/miniconda3/bin/conda', - ].forEach((knownLocation) => { - test(`Must return conda path from known location '${knownLocation}' (non windows)`, async () => { - const expectedCondaLocation = untildify(knownLocation); - platformService.setup((p) => p.isWindows).returns(() => false); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.reject(new Error('Not Found'))); - fileSystem - .setup((fs) => fs.search(TypeMoq.It.isAny())) - .returns(() => Promise.resolve([expectedCondaLocation])); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(expectedCondaLocation))) - .returns(() => Promise.resolve(true)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, expectedCondaLocation, 'Failed to identify'); - }); - }); - - test("Must return 'conda' if conda could not be found in known locations", async () => { - platformService.setup((p) => p.isWindows).returns(() => false); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - fileSystem.setup((fs) => fs.search(TypeMoq.It.isAny())).returns(() => Promise.resolve([])); - fileSystem.setup((fs) => fs.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, 'conda', 'Failed to identify'); - }); - - test('Correctly identify interpreter location relative to entironment path (non windows)', async () => { - const environmentPath = path.join('a', 'b', 'c'); - platformService.setup((p) => p.isWindows).returns(() => false); - const pythonPath = condaService.getInterpreterPath(environmentPath); - assert.equal(pythonPath, path.join(environmentPath, 'bin', 'python'), 'Incorrect path'); - }); - - test('Correctly identify interpreter location relative to entironment path (windows)', async () => { - const environmentPath = path.join('a', 'b', 'c'); - platformService.setup((p) => p.isWindows).returns(() => true); - const pythonPath = condaService.getInterpreterPath(environmentPath); - assert.equal(pythonPath, path.join(environmentPath, 'python.exe'), 'Incorrect path'); - }); - - test('Returns condaInfo when conda exists', async () => { - const expectedInfo = { - envs: [ - path.join(environmentsPath, 'conda', 'envs', 'numpy'), - path.join(environmentsPath, 'conda', 'envs', 'scipy'), - ], - default_prefix: '', - 'sys.version': - '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]', - }; - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['info', '--json']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.resolve({ stdout: JSON.stringify(expectedInfo) })); - - const condaInfo = await condaService.getCondaInfo(); - assert.deepEqual(condaInfo, expectedInfo, 'Conda info does not match'); - }); - - test("Returns undefined if there's and error in getting the info", async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['info', '--json']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.reject(new Error('unknown'))); - - const condaInfo = await condaService.getCondaInfo(); - assert.equal(condaInfo, undefined, 'Conda info does not match'); - }); - - test('Returns conda environments when conda exists', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: '' })); - const environments = await condaService.getCondaEnvironments(true); - assert.equal(environments, undefined, 'Conda environments do not match'); - }); - - test('Logs information message when conda does not exist', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - const environments = await condaService.getCondaEnvironments(true); - assert.equal(environments, undefined, 'Conda environments do not match'); - }); - - test('Returns cached conda environments', async () => { - resetMockState({ data: 'CachedInfo' }); - - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: '' })); - const environments = await condaService.getCondaEnvironments(false); - assert.equal(environments, 'CachedInfo', 'Conda environments do not match'); - }); - - test('Subsequent list of environments will be retrieved from cache', async () => { - const envList = [ - '# conda environments:', - '#', - 'base * /Users/donjayamanne/anaconda3', - 'one /Users/donjayamanne/anaconda3/envs/one', - 'one two /Users/donjayamanne/anaconda3/envs/one two', - 'py27 /Users/donjayamanne/anaconda3/envs/py27', - 'py36 /Users/donjayamanne/anaconda3/envs/py36', - 'three /Users/donjayamanne/anaconda3/envs/three', - ]; - - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: envList.join(EOL) })); - const environments = await condaService.getCondaEnvironments(false); - expect(environments).lengthOf(6, 'Incorrect number of environments'); - expect(mockState.data.data).lengthOf(6, 'Incorrect number of environments in cache'); - - mockState.data.data = []; - const environmentsFetchedAgain = await condaService.getCondaEnvironments(false); - expect(environmentsFetchedAgain).lengthOf(0, 'Incorrect number of environments fetched from cache'); - }); - - test("Returns undefined if there's and error in getting the info", async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['info', '--json']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.reject(new Error('unknown'))); - - const condaInfo = await condaService.getCondaInfo(); - assert.equal(condaInfo, undefined, 'Conda info does not match'); - }); - - test('Must use Conda env from Registry to locate conda.exe', async () => { - const condaPythonExePath = path.join( - __dirname, - '..', - '..', - '..', - 'src', - 'test', - 'pythonFiles', - 'environments', - 'conda', - 'Scripts', - 'python.exe', - ); - const registryInterpreters: PythonEnvironment[] = [ - { - displayName: 'One', - path: path.join(environmentsPath, 'path1', 'one.exe'), - companyDisplayName: 'One 1', - version: new SemVer('1.0.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Anaconda', - path: condaPythonExePath, - companyDisplayName: 'Two 2', - version: new SemVer('1.11.0'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Three', - path: path.join(environmentsPath, 'path2', 'one.exe'), - companyDisplayName: 'Three 3', - version: new SemVer('2.10.1'), - envType: EnvironmentType.Unknown, - }, - { - displayName: 'Seven', - path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - companyDisplayName: 'Continuum Analytics, Inc.', - envType: EnvironmentType.Unknown, - }, - ].map((item) => ({ ...info, ...item })); - - const expectedCodaExe = path.join(path.dirname(condaPythonExePath), 'conda.exe'); - - platformService.setup((p) => p.isWindows).returns(() => true); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Not Found'))); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(expectedCodaExe))) - .returns(() => Promise.resolve(true)); - registryInterpreterLocatorService - .setup((r) => r.getInterpreters(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(registryInterpreters)); - - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, expectedCodaExe, 'Failed to identify conda.exe'); - }); - - test('isCondaInCurrentPath will return true if conda is available', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'xyz' })); - const isAvailable = await condaService.isCondaInCurrentPath(); - assert.equal(isAvailable, true); - }); - - test('isCondaInCurrentPath will return false if conda is not available', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('not found'))); - fileSystem.setup((fs) => fs.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); - platformService.setup((p) => p.isWindows).returns(() => false); - - const isAvailable = await condaService.isCondaInCurrentPath(); - assert.equal(isAvailable, false); - }); - - async function testFailureOfGettingCondaEnvironments( - isWindows: boolean, - isOsx: boolean, - isLinux: boolean, - pythonPath: string, - ) { - platformService.setup((p) => p.isLinux).returns(() => isLinux); - platformService.setup((p) => p.isWindows).returns(() => isWindows); - platformService.setup((p) => p.isMac).returns(() => isOsx); - - resetMockState({ data: undefined }); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: 'some value' })); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())) - .returns(() => Promise.reject(new Error('Failed'))); - const condaEnv = await condaService.getCondaEnvironment(pythonPath); - expect(condaEnv).to.be.equal(undefined, 'Conda should be undefined'); - } - test('Fails to identify an environment as a conda env (windows)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python.exe'); - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await testFailureOfGettingCondaEnvironments(true, false, false, pythonPath); - }); - test('Fails to identify an environment as a conda env (linux)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python'); - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await testFailureOfGettingCondaEnvironments(false, false, true, pythonPath); - }); - test('Fails to identify an environment as a conda env (osx)', async () => { - const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python'); - fileSystem - .setup((f) => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))) - .returns(() => Promise.resolve(true)); - await testFailureOfGettingCondaEnvironments(false, true, false, pythonPath); - }); - - type InterpreterSearchTestParams = { - pythonPath: string; - environmentName: string; - isLinux: boolean; - expectedCondaPath: string; - }; - - const testsForInterpreter: InterpreterSearchTestParams[] = [ - { - pythonPath: path.join('users', 'foo', 'envs', 'test1', 'python'), - environmentName: 'test1', - isLinux: true, - expectedCondaPath: path.join('users', 'foo', 'bin', 'conda'), - }, - { - pythonPath: path.join('users', 'foo', 'envs', 'test2', 'python'), - environmentName: 'test2', - isLinux: true, - expectedCondaPath: path.join('users', 'foo', 'envs', 'test2', 'conda'), - }, - { - pythonPath: path.join('users', 'foo', 'envs', 'test3', 'python'), - environmentName: 'test3', - isLinux: false, - expectedCondaPath: path.join('users', 'foo', 'Scripts', 'conda.exe'), - }, - { - pythonPath: path.join('users', 'foo', 'envs', 'test4', 'python'), - environmentName: 'test4', - isLinux: false, - expectedCondaPath: path.join('users', 'foo', 'conda.exe'), - }, - ]; - - testsForInterpreter.forEach((t) => { - test(`Finds conda.exe for subenvironment ${t.environmentName}`, async () => { - platformService.setup((p) => p.isLinux).returns(() => t.isLinux); - platformService.setup((p) => p.isWindows).returns(() => !t.isLinux); - platformService.setup((p) => p.isMac).returns(() => false); - fileSystem - .setup((f) => - f.fileExists( - TypeMoq.It.is((p) => { - if (p === t.expectedCondaPath) { - return true; - } - return false; - }), - ), - ) - .returns(() => Promise.resolve(true)); - - const condaFile = await condaService._getCondaFileFromInterpreter(t.pythonPath, t.environmentName); - assert.equal(condaFile, t.expectedCondaPath); - }); - test(`Finds conda.exe for different ${t.environmentName}`, async () => { - platformService.setup((p) => p.isLinux).returns(() => t.isLinux); - platformService.setup((p) => p.isWindows).returns(() => !t.isLinux); - platformService.setup((p) => p.isMac).returns(() => false); - fileSystem - .setup((f) => - f.fileExists( - TypeMoq.It.is((p) => { - if (p === t.expectedCondaPath) { - return true; - } - return false; - }), - ), - ) - .returns(() => Promise.resolve(true)); - - const condaFile = await condaService._getCondaFileFromInterpreter(t.pythonPath, undefined); - - // This should only work if the expectedConda path has the original environment name in it - if (t.expectedCondaPath.includes(t.environmentName)) { - assert.equal(condaFile, t.expectedCondaPath); - } else { - assert.equal(condaFile, undefined); - } - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts index afeac855644a..0896fd9d1627 100644 --- a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts @@ -6,27 +6,20 @@ import { IWorkspaceService } from '../../../../client/common/application/types'; import { FileSystemPaths, FileSystemPathUtils } from '../../../../client/common/platform/fs-paths'; import { IFileSystem, IPlatformService } from '../../../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../../../client/common/process/types'; -import { IConfigurationService, IPythonSettings } from '../../../../client/common/types'; -import { CondaService } from '../../../../client/pythonEnvironments/discovery/locators/services/condaService'; +import { CondaService } from '../../../../client/pythonEnvironments/common/environmentManagers/condaService'; suite('Interpreters Conda Service', () => { let processService: TypeMoq.IMock; let platformService: TypeMoq.IMock; let condaService: CondaService; let fileSystem: TypeMoq.IMock; - let config: TypeMoq.IMock; - let settings: TypeMoq.IMock; let procServiceFactory: TypeMoq.IMock; - let condaPathSetting: string; let workspaceService: TypeMoq.IMock; setup(async () => { - condaPathSetting = ''; processService = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - config = TypeMoq.Mock.ofType(); - settings = TypeMoq.Mock.ofType(); procServiceFactory = TypeMoq.Mock.ofType(); // eslint-disable-next-line @typescript-eslint/no-explicit-any processService.setup((x: any) => x.then).returns(() => undefined); @@ -34,8 +27,6 @@ suite('Interpreters Conda Service', () => { .setup((p) => p.create(TypeMoq.It.isAny())) .returns(() => Promise.resolve(processService.object)); - config.setup((c) => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => settings.object); - settings.setup((p) => p.condaPath).returns(() => condaPathSetting); fileSystem .setup((fs) => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns((p1, p2) => { @@ -49,7 +40,6 @@ suite('Interpreters Conda Service', () => { procServiceFactory.object, platformService.object, fileSystem.object, - config.object, [], workspaceService.object, ); diff --git a/src/test/pythonEnvironments/discovery/locators/hashProvider.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/hashProvider.unit.test.ts deleted file mode 100644 index 7045d1670992..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/hashProvider.unit.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect, use } from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import * as sinon from 'sinon'; -import * as fsapi from 'fs-extra'; -import * as hashApi from '../../../../client/common/platform/fileSystem'; -import { getInterpreterHash } from '../../../../client/pythonEnvironments/discovery/locators/services/hashProvider'; - -use(chaiAsPromised); - -suite('Interpreters - Interpreter Hash Provider', () => { - let fsLStatStub: sinon.SinonStub; - let hashStub: sinon.SinonStub; - setup(() => { - fsLStatStub = sinon.stub(fsapi, 'lstat'); - hashStub = sinon.stub(hashApi, 'getHashString'); - hashStub.resolves('hash'); - }); - teardown(() => { - fsLStatStub.restore(); - hashStub.restore(); - }); - test('Get hash from fs', async () => { - const pythonPath = 'some/python.exe'; - const now = Date.now(); - fsLStatStub.withArgs(pythonPath).resolves({ - ctime: now, - mtime: now, - }); - const hash = await getInterpreterHash(pythonPath); - - expect(hash).to.equal('hash'); - expect(fsLStatStub.calledOnceWith(pythonPath)).to.equal(true); - }); - test('Get hash from fs for windows store python', async () => { - const pythonPath = 'winstore/python.exe'; - const now = Date.now(); - fsLStatStub.withArgs(pythonPath).throws({ code: 'UNKNOWN' }); - fsLStatStub.withArgs('winstore').resolves({ - ctime: now, - mtime: now, - }); - const hash = await getInterpreterHash(pythonPath); - - expect(hash).to.equal('hash'); - expect(fsLStatStub.calledTwice).to.equal(true); - }); - test('Exception from getInterpreterHash will be bubbled up', async () => { - const pythonPath = 'winstore/python.exe'; - fsLStatStub.withArgs(pythonPath).rejects({ code: 'UNKNOWN' }); - fsLStatStub.withArgs('winstore').rejects(new Error('Kaboom')); - const promise = getInterpreterHash(pythonPath); - - await expect(promise).to.eventually.be.rejectedWith('Kaboom'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts deleted file mode 100644 index 45438f591c4e..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/helpers.unit.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import { anything, instance, mock, when } from 'ts-mockito'; -import * as TypeMoq from 'typemoq'; -import { IFileSystem, IPlatformService } from '../../../../client/common/platform/types'; -import { getNamesAndValues } from '../../../../client/common/utils/enum'; -import { Architecture } from '../../../../client/common/utils/platform'; -import { IInterpreterHelper, IInterpreterLocatorHelper } from '../../../../client/interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../../client/interpreter/locators/types'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { InterpreterLocatorHelper } from '../../../../client/pythonEnvironments/discovery/locators/helpers'; -import { PipEnvServiceHelper } from '../../../../client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper'; -import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; - -enum OS { - Windows = 'Windows', - Linux = 'Linux', - Mac = 'Mac', -} - -suite('Interpreters - Locators Helper', () => { - let serviceContainer: TypeMoq.IMock; - let platform: TypeMoq.IMock; - let helper: IInterpreterLocatorHelper; - let fs: TypeMoq.IMock; - let pipEnvHelper: IPipEnvServiceHelper; - let interpreterServiceHelper: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - platform = TypeMoq.Mock.ofType(); - fs = TypeMoq.Mock.ofType(); - pipEnvHelper = mock(PipEnvServiceHelper); - interpreterServiceHelper = TypeMoq.Mock.ofType(); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platform.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fs.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterHelper))) - .returns(() => interpreterServiceHelper.object); - - helper = new InterpreterLocatorHelper(fs.object, instance(pipEnvHelper)); - }); - test('Ensure default Mac interpreter is not excluded from the list of interpreters', async () => { - platform.setup((p) => p.isWindows).returns(() => false); - platform.setup((p) => p.isLinux).returns(() => false); - platform - .setup((p) => p.isMac) - .returns(() => true) - .verifiable(TypeMoq.Times.never()); - fs.setup((f) => f.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => false) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const interpreters: PythonEnvironment[] = []; - ['conda', 'virtualenv', 'mac', 'pyenv'].forEach((name) => { - const interpreter = { - architecture: Architecture.Unknown, - displayName: name, - path: path.join('users', 'python', 'bin', name), - sysPrefix: name, - sysVersion: name, - envType: EnvironmentType.Unknown, - version: new SemVer('0.0.0-alpha'), - }; - interpreters.push(interpreter); - - // Treat 'mac' as as mac interpreter. - interpreterServiceHelper - .setup((i) => i.isMacDefaultPythonPath(TypeMoq.It.isValue(interpreter.path))) - .returns(() => Promise.resolve(name === 'mac')) - .verifiable(TypeMoq.Times.never()); - }); - - const expectedInterpreters = interpreters.slice(0); - when(pipEnvHelper.getPipEnvInfo(anything())).thenResolve(); - - const items = await helper.mergeInterpreters(interpreters); - - interpreterServiceHelper.verifyAll(); - platform.verifyAll(); - fs.verifyAll(); - expect(items).to.be.lengthOf(4); - expect(items).to.be.deep.equal(expectedInterpreters); - }); - 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(() => 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); - fs.setup((f) => f.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((a, b) => a === b) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const interpreters: PythonEnvironment[] = []; - const expectedInterpreters: PythonEnvironment[] = []; - // Unique python paths and versions. - ['3.6', '3.6', '2.7', '2.7'].forEach((name, index) => { - const interpreter = { - architecture: Architecture.Unknown, - displayName: name, - path: path.join('users', `python${name}${index}`, 'bin', name + index.toString()), - sysPrefix: name, - sysVersion: name, - envType: EnvironmentType.Unknown, - version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`), - }; - interpreters.push(interpreter); - expectedInterpreters.push(interpreter); - }); - // Same versions, but different executables. - ['3.6', '3.6', '3.7', '3.7'].forEach((name, index) => { - const interpreter = { - architecture: Architecture.Unknown, - displayName: name, - path: path.join('users', 'python', 'bin', 'python.exe'), - sysPrefix: name, - sysVersion: name, - envType: EnvironmentType.Unknown, - version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`), - }; - - const duplicateInterpreter = { - architecture: Architecture.Unknown, - displayName: name, - path: path.join('users', 'python', 'bin', `python${name}.exe`), - sysPrefix: name, - sysVersion: name, - envType: EnvironmentType.Unknown, - version: new SemVer(interpreter.version.raw), - }; - - interpreters.push(interpreter); - interpreters.push(duplicateInterpreter); - if (index % 2 === 1) { - expectedInterpreters.push(interpreter); - } - }); - - when(pipEnvHelper.getPipEnvInfo(anything())).thenResolve(); - const items = await helper.mergeInterpreters(interpreters); - - interpreterServiceHelper.verifyAll(); - platform.verifyAll(); - fs.verifyAll(); - expect(items).to.be.lengthOf(expectedInterpreters.length); - expect(items).to.be.deep.equal(expectedInterpreters); - }); - }); - 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(() => 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); - fs.setup((f) => f.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((a, b) => a === b && a === path.join('users', 'python', 'bin')) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const interpreters: PythonEnvironment[] = []; - const expectedInterpreters: PythonEnvironment[] = []; - ['3.6', '3.6'].forEach((name, index) => { - // Ensure the type in the first item is 'Unknown', - // and type in second item is known (e.g. Conda). - const type = index === 0 ? EnvironmentType.Unknown : EnvironmentType.Pipenv; - const interpreter = { - architecture: Architecture.Unknown, - displayName: name, - path: path.join('users', 'python', 'bin', 'python.exe'), - sysPrefix: name, - sysVersion: name, - envType: type, - version: new SemVer(`3.${parseInt(name.substr(-1), 10)}.0-final`), - }; - interpreters.push(interpreter); - - if (index === 1) { - expectedInterpreters.push(interpreter); - } - }); - - when(pipEnvHelper.getPipEnvInfo(anything())).thenResolve(); - const items = await helper.mergeInterpreters(interpreters); - - interpreterServiceHelper.verifyAll(); - platform.verifyAll(); - fs.verifyAll(); - expect(items).to.be.lengthOf(1); - expect(items).to.be.deep.equal(expectedInterpreters); - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/interpreterFilter.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/interpreterFilter.unit.test.ts deleted file mode 100644 index 76e21d7495be..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/interpreterFilter.unit.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect } from 'chai'; -import { isHiddenInterpreter } from '../../../../client/pythonEnvironments/discovery/locators/services/interpreterFilter'; -import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; - -suite('Interpreters - Filter', () => { - const doNotHideThesePaths = [ - 'python', - 'python.exe', - 'python2', - 'python2.exe', - 'python38', - 'python3.8.exe', - 'C:\\Users\\SomeUser\\AppData\\Local\\Microsoft\\WindowsApps\\python.exe', - '%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps\\python.exe', - '%LOCALAPPDATA%\\Microsoft\\WindowsApps\\python.exe', - ]; - const hideThesePaths = [ - '%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\python.exe', - 'C:\\Users\\SomeUser\\AppData\\Local\\Microsoft\\WindowsApps\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\python.exe', - '%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps\\PythonSoftwareFoundation\\python.exe', - 'C:\\Users\\SomeUser\\AppData\\Local\\Microsoft\\WindowsApps\\PythonSoftwareFoundation\\python.exe', - '%LOCALAPPDATA%\\Microsoft\\WindowsApps\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\python.exe', - '%LOCALAPPDATA%\\Microsoft\\WindowsApps\\PythonSoftwareFoundation\\python.exe', - 'C:\\Program Files\\WindowsApps\\python.exe', - 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\python.exe', - 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation\\python.exe', - ]; - - function getInterpreterFromPath(interpreterPath: string): PythonEnvironment { - return { - path: interpreterPath, - sysVersion: '', - sysPrefix: '', - architecture: 1, - companyDisplayName: '', - displayName: 'python', - envType: EnvironmentType.WindowsStore, - envName: '', - envPath: '', - cachedEntry: false, - }; - } - - doNotHideThesePaths.forEach((interpreterPath) => { - test(`Interpreter path should NOT be hidden - ${interpreterPath}`, () => { - const interpreter: PythonEnvironment = getInterpreterFromPath(interpreterPath); - - expect(isHiddenInterpreter(interpreter), `${interpreterPath} should NOT be treated as hidden.`).to.equal( - false, - ); - }); - }); - hideThesePaths.forEach((interpreterPath) => { - test(`Interpreter path should be hidden - ${interpreterPath}`, () => { - const interpreter: PythonEnvironment = getInterpreterFromPath(interpreterPath); - - expect(isHiddenInterpreter(interpreter), `${interpreterPath} should be treated as hidden.`).to.equal(true); - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/interpreterLocatorService.testvirtualenvs.ts b/src/test/pythonEnvironments/discovery/locators/interpreterLocatorService.testvirtualenvs.ts deleted file mode 100644 index 946f771f574b..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/interpreterLocatorService.testvirtualenvs.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import { RegistryImplementation } from '../../../../client/common/platform/registry'; -import { IRegistry } from '../../../../client/common/platform/types'; -import { IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE } from '../../../../client/interpreter/contracts'; -import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; -import { getOSType, OSType } from '../../../common'; -import { TEST_TIMEOUT } from '../../../constants'; -import { closeActiveWindows, initialize, initializeTest } from '../../../initialize'; -import { UnitTestIocContainer } from '../../../testing/serviceRegistry'; - -suite('Python interpreter locator service', () => { - let ioc: UnitTestIocContainer; - let interpreters: PythonEnvironment[]; - suiteSetup(async function () { - // https://github.com/microsoft/vscode-python/issues/12634 - - return this.skip(); - - this.timeout(getOSType() === OSType.Windows ? TEST_TIMEOUT * 7 : TEST_TIMEOUT * 2); - await initialize(); - await initializeDI(); - const locator = ioc.serviceContainer.get( - IInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE, - ); - interpreters = await locator.getInterpreters(); - }); - - setup(async () => { - await initializeTest(); - await initializeDI(); - }); - - teardown(async () => { - await ioc.dispose(); - await closeActiveWindows(); - }); - suiteTeardown(closeActiveWindows); - - async function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerUnitTestTypes(); - ioc.registerMockProcessTypes(); - ioc.registerVariableTypes(); - ioc.registerInterpreterTypes(); - ioc.serviceManager.addSingleton(IRegistry, RegistryImplementation); - } - - test('Ensure we are getting conda environment created using command `conda create -n "test_env1" -y python`', async () => { - // Created in CI using command `conda create -n "test_env1" -y python` - const filteredInterpreters = interpreters.filter( - (i) => i.envName === 'test_env1' && i.envType === EnvironmentType.Conda, - ); - expect(filteredInterpreters.length).to.be.greaterThan(0, 'Environment test_env1 not found'); - }); - test('Ensure we are getting conda environment created using command `conda create -p "./test_env2`"', async () => { - // Created in CI using command `conda create -p "./test_env2" -y python` - const filteredInterpreters = interpreters.filter((i) => { - let dirName = path.dirname(i.path); - if (dirName.endsWith('bin') || dirName.endsWith('Scripts')) { - dirName = path.dirname(dirName); - } - return dirName.endsWith('test_env2') && i.envType === EnvironmentType.Conda; - }); - expect(filteredInterpreters.length).to.be.greaterThan(0, 'Environment test_env2 not found'); - }); - test('Ensure we are getting conda environment created using command `conda create -p "/test_env3" -y python`', async () => { - // Created in CI using command `conda create -p "/test_env3" -y python` - const filteredInterpreters = interpreters.filter((i) => { - let dirName = path.dirname(i.path); - if (dirName.endsWith('bin') || dirName.endsWith('Scripts')) { - dirName = path.dirname(dirName); - } - return dirName.endsWith('test_env3') && i.envType === EnvironmentType.Conda; - }); - expect(filteredInterpreters.length).to.be.greaterThan(0, 'Environment test_env3 not found'); - }); - - test('Ensure we are getting the base conda environment', async () => { - // Base conda environment in CI - const filteredInterpreters = interpreters.filter( - (i) => (i.envName === 'base' || i.envName === 'miniconda') && i.envType === EnvironmentType.Conda, - ); - expect(filteredInterpreters.length).to.be.greaterThan(0, 'Base environment not found'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/interpreterWatcherBuilder.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/interpreterWatcherBuilder.unit.test.ts deleted file mode 100644 index 6bb904ed7fd8..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/interpreterWatcherBuilder.unit.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { anything, instance, mock, when } from 'ts-mockito'; -import { WorkspaceService } from '../../../../client/common/application/workspace'; -import { IInterpreterWatcher, WORKSPACE_VIRTUAL_ENV_SERVICE } from '../../../../client/interpreter/contracts'; -import { ServiceContainer } from '../../../../client/ioc/container'; -import { InterpreterWatcherBuilder } from '../../../../client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder'; - -suite('Interpreters - Watcher Builder', () => { - test('Build Workspace Virtual Env Watcher', async () => { - const workspaceService = mock(WorkspaceService); - const serviceContainer = mock(ServiceContainer); - const builder = new InterpreterWatcherBuilder(instance(workspaceService), instance(serviceContainer)); - const watcher = { register: () => Promise.resolve() }; - - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(); - when(serviceContainer.get(IInterpreterWatcher, WORKSPACE_VIRTUAL_ENV_SERVICE)).thenReturn( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (watcher as any) as IInterpreterWatcher, - ); - - const item = await builder.getWorkspaceVirtualEnvInterpreterWatcher(undefined); - - expect(item).to.be.equal(watcher, 'invalid'); - }); - test('Ensure we cache Workspace Virtual Env Watcher', async () => { - const workspaceService = mock(WorkspaceService); - const serviceContainer = mock(ServiceContainer); - const builder = new InterpreterWatcherBuilder(instance(workspaceService), instance(serviceContainer)); - const watcher = { register: () => Promise.resolve() }; - - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(); - when(serviceContainer.get(IInterpreterWatcher, WORKSPACE_VIRTUAL_ENV_SERVICE)).thenReturn( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (watcher as any) as IInterpreterWatcher, - ); - - const [item1, item2, item3] = await Promise.all([ - builder.getWorkspaceVirtualEnvInterpreterWatcher(undefined), - builder.getWorkspaceVirtualEnvInterpreterWatcher(undefined), - builder.getWorkspaceVirtualEnvInterpreterWatcher(undefined), - ]); - - expect(item1).to.be.equal(watcher, 'invalid'); - expect(item2).to.be.equal(watcher, 'invalid'); - expect(item3).to.be.equal(watcher, 'invalid'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/knownPathService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/knownPathService.unit.test.ts deleted file mode 100644 index df307f85d765..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/knownPathService.unit.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { IPlatformService } from '../../../../client/common/platform/types'; -import { ICurrentProcess, IPathUtils } from '../../../../client/common/types'; -import { IKnownSearchPathsForInterpreters } from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { KnownSearchPathsForInterpreters } from '../../../../client/pythonEnvironments/discovery/locators/services/KnownPathsService'; - -suite('Interpreters Known Paths', () => { - let serviceContainer: TypeMoq.IMock; - let currentProcess: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - let pathUtils: TypeMoq.IMock; - let knownSearchPaths: IKnownSearchPathsForInterpreters; - - setup(async () => { - serviceContainer = TypeMoq.Mock.ofType(); - currentProcess = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - pathUtils = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICurrentProcess), TypeMoq.It.isAny())) - .returns(() => currentProcess.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())) - .returns(() => platformService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPathUtils), TypeMoq.It.isAny())) - .returns(() => pathUtils.object); - - knownSearchPaths = new KnownSearchPathsForInterpreters(serviceContainer.object); - }); - - test('Ensure known list of paths are returned', async () => { - const pathDelimiter = 'X'; - const pathsInPATHVar = [path.join('a', 'b', 'c'), '', path.join('1', '2'), '3']; - pathUtils.setup((p) => p.delimiter).returns(() => pathDelimiter); - platformService.setup((p) => p.isWindows).returns(() => true); - platformService.setup((p) => p.pathVariableName).returns(() => 'PATH'); - currentProcess.setup((p) => p.env).returns(() => ({ PATH: pathsInPATHVar.join(pathDelimiter) })); - - const expectedPaths = [...pathsInPATHVar].filter((item) => item.length > 0); - - const paths = knownSearchPaths.getSearchPaths(); - - expect(paths).to.deep.equal(expectedPaths); - }); - - test('Ensure known list of paths are returned on non-windows', async () => { - const homeDir = '/users/peter Smith'; - const pathDelimiter = 'X'; - pathUtils.setup((p) => p.delimiter).returns(() => pathDelimiter); - pathUtils.setup((p) => p.home).returns(() => homeDir); - platformService.setup((p) => p.isWindows).returns(() => false); - platformService.setup((p) => p.pathVariableName).returns(() => 'PATH'); - currentProcess.setup((p) => p.env).returns(() => ({ PATH: '' })); - - const expectedPaths: string[] = []; - ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'].forEach((p) => { - expectedPaths.push(p); - expectedPaths.push(path.join(homeDir, p)); - }); - - expectedPaths.push(path.join(homeDir, 'anaconda', 'bin')); - expectedPaths.push(path.join(homeDir, 'python', 'bin')); - - const paths = knownSearchPaths.getSearchPaths(); - - expect(paths).to.deep.equal(expectedPaths); - }); - - test('Ensure PATH variable and known list of paths are merged on non-windows', async () => { - const homeDir = '/users/peter Smith'; - const pathDelimiter = 'X'; - const pathsInPATHVar = [path.join('a', 'b', 'c'), '', path.join('1', '2'), '3']; - pathUtils.setup((p) => p.delimiter).returns(() => pathDelimiter); - pathUtils.setup((p) => p.home).returns(() => homeDir); - platformService.setup((p) => p.isWindows).returns(() => false); - platformService.setup((p) => p.pathVariableName).returns(() => 'PATH'); - currentProcess.setup((p) => p.env).returns(() => ({ PATH: pathsInPATHVar.join(pathDelimiter) })); - - const expectedPaths = [...pathsInPATHVar].filter((item) => item.length > 0); - ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'].forEach((p) => { - expectedPaths.push(p); - expectedPaths.push(path.join(homeDir, p)); - }); - - expectedPaths.push(path.join(homeDir, 'anaconda', 'bin')); - expectedPaths.push(path.join(homeDir, 'python', 'bin')); - - const paths = knownSearchPaths.getSearchPaths(); - - expect(paths).to.deep.equal(expectedPaths); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/pipEnvService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/pipEnvService.unit.test.ts deleted file mode 100644 index 872bff8b720a..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/pipEnvService.unit.test.ts +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { expect } from 'chai'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import * as sinon from 'sinon'; -import { anything, instance, mock, when } from 'ts-mockito'; -import * as TypeMoq from 'typemoq'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../../../client/common/application/types'; -import { IFileSystem, IPlatformService } from '../../../../client/common/platform/types'; -import { IProcessService, IProcessServiceFactory } from '../../../../client/common/process/types'; -import { - IConfigurationService, - ICurrentProcess, - IPersistentState, - IPersistentStateFactory, - IPythonSettings, -} from '../../../../client/common/types'; -import { getNamesAndValues } from '../../../../client/common/utils/enum'; -import { IEnvironmentVariablesProvider } from '../../../../client/common/variables/types'; -import { IInterpreterHelper } from '../../../../client/interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../../client/interpreter/locators/types'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { PipEnvService } from '../../../../client/pythonEnvironments/discovery/locators/services/pipEnvService'; -import { PipEnvServiceHelper } from '../../../../client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper'; -import * as Telemetry from '../../../../client/telemetry'; -import { EventName } from '../../../../client/telemetry/constants'; - -enum OS { - Mac, - Windows, - Linux, -} - -suite('Interpreters - PipEnv', () => { - const rootWorkspace = Uri.file(path.join('usr', 'desktop', 'wkspc1')).fsPath; - getNamesAndValues(OS).forEach((os) => { - [undefined, Uri.file(path.join(rootWorkspace, 'one.py'))].forEach((resource) => { - const testSuffix = ` (${os.name}, ${resource ? 'with' : 'without'} a workspace)`; - - let pipEnvService: PipEnvService; - let serviceContainer: TypeMoq.IMock; - let interpreterHelper: TypeMoq.IMock; - let processService: TypeMoq.IMock; - let currentProcess: TypeMoq.IMock; - let fileSystem: TypeMoq.IMock; - let appShell: TypeMoq.IMock; - let persistentStateFactory: TypeMoq.IMock; - let envVarsProvider: TypeMoq.IMock; - let procServiceFactory: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - let config: TypeMoq.IMock; - let settings: TypeMoq.IMock; - let pipenvPathSetting: string; - let pipEnvServiceHelper: IPipEnvServiceHelper; - - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - const workspaceService = TypeMoq.Mock.ofType(); - interpreterHelper = TypeMoq.Mock.ofType(); - fileSystem = TypeMoq.Mock.ofType(); - processService = TypeMoq.Mock.ofType(); - appShell = TypeMoq.Mock.ofType(); - currentProcess = TypeMoq.Mock.ofType(); - persistentStateFactory = TypeMoq.Mock.ofType(); - envVarsProvider = TypeMoq.Mock.ofType(); - procServiceFactory = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - pipEnvServiceHelper = mock(PipEnvServiceHelper); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - processService.setup((x: any) => x.then).returns(() => undefined); - procServiceFactory - .setup((p) => p.create(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(processService.object)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const persistentState = TypeMoq.Mock.ofType>(); - persistentStateFactory - .setup((p) => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => persistentState.object); - persistentStateFactory - .setup((p) => p.createWorkspacePersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => persistentState.object); - persistentState.setup((p) => p.value).returns(() => undefined); - persistentState.setup((p) => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); - - const workspaceFolder = TypeMoq.Mock.ofType(); - workspaceFolder.setup((w) => w.uri).returns(() => Uri.file(rootWorkspace)); - workspaceService - .setup((w) => w.getWorkspaceFolder(TypeMoq.It.isAny())) - .returns(() => workspaceFolder.object); - workspaceService.setup((w) => w.rootPath).returns(() => rootWorkspace); - - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())) - .returns(() => procServiceFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterHelper))) - .returns(() => interpreterHelper.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICurrentProcess))) - .returns(() => currentProcess.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))) - .returns(() => appShell.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPersistentStateFactory))) - .returns(() => persistentStateFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))) - .returns(() => envVarsProvider.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPlatformService))) - .returns(() => platformService.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) - .returns(() => config.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPipEnvServiceHelper), TypeMoq.It.isAny())) - .returns(() => instance(pipEnvServiceHelper)); - - when(pipEnvServiceHelper.trackWorkspaceFolder(anything(), anything())).thenResolve(); - config = TypeMoq.Mock.ofType(); - settings = TypeMoq.Mock.ofType(); - config.setup((c) => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => settings.object); - settings.setup((p) => p.pipenvPath).returns(() => pipenvPathSetting); - pipenvPathSetting = 'pipenv'; - - pipEnvService = new PipEnvService(serviceContainer.object); - }); - - suite('With didTriggerInterpreterSuggestions set to true', () => { - setup(() => { - sinon.stub(pipEnvService, 'didTriggerInterpreterSuggestions').get(() => true); - }); - - teardown(() => { - sinon.restore(); - }); - - test(`Should return an empty list'${testSuffix}`, () => { - const environments = pipEnvService.getInterpreters(resource); - expect(environments).to.be.eventually.deep.equal([]); - }); - test(`Should return an empty list if there is no \'PipFile\'${testSuffix}`, async () => { - const env = {}; - envVarsProvider - .setup((e) => e.getEnvironmentVariables(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({})) - .verifiable(TypeMoq.Times.once()); - currentProcess.setup((c) => c.env).returns(() => env); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - const environments = await pipEnvService.getInterpreters(resource); - - expect(environments).to.be.deep.equal([]); - fileSystem.verifyAll(); - }); - test(`Should display warning message if there is a \'PipFile\' but \'pipenv --version\' fails ${testSuffix}`, async () => { - const env = {}; - currentProcess.setup((c) => c.env).returns(() => env); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.reject(new Error())); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))) - .returns(() => Promise.resolve(true)); - const warningMessage = - "Workspace contains Pipfile but 'pipenv' was not found. Make sure 'pipenv' is on the PATH."; - appShell - .setup((a) => a.showWarningMessage(warningMessage)) - .returns(() => Promise.resolve('')) - .verifiable(TypeMoq.Times.once()); - const environments = await pipEnvService.getInterpreters(resource); - - expect(environments).to.be.deep.equal([]); - appShell.verifyAll(); - }); - test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' fails with stderr ${testSuffix}`, async () => { - const env = {}; - currentProcess.setup((c) => c.env).returns(() => env); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.resolve({ stderr: '', stdout: 'pipenv, version 2018.11.26' })); - processService - .setup((p) => - p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isValue(['--venv']), TypeMoq.It.isAny()), - ) - .returns(() => Promise.resolve({ stderr: 'Aborted!', stdout: '' })); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))) - .returns(() => Promise.resolve(true)); - const warningMessage = - 'Workspace contains Pipfile but the associated virtual environment has not been setup. Setup the virtual environment manually if needed.'; - appShell - .setup((a) => a.showWarningMessage(warningMessage)) - .returns(() => Promise.resolve('')) - .verifiable(TypeMoq.Times.once()); - const environments = await pipEnvService.getInterpreters(resource); - - expect(environments).to.be.deep.equal([]); - appShell.verifyAll(); - }); - test(`Should return interpreter information${testSuffix}`, async () => { - const env = {}; - const pythonPath = 'one'; - envVarsProvider - .setup((e) => e.getEnvironmentVariables(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({})) - .verifiable(TypeMoq.Times.once()); - currentProcess.setup((c) => c.env).returns(() => env); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: pythonPath })); - interpreterHelper - .setup((v) => v.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: new SemVer('1.0.0') })); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))) - .returns(() => Promise.resolve(true)) - .verifiable(); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)) - .verifiable(); - - const environments = await pipEnvService.getInterpreters(resource); - - expect(environments).to.be.lengthOf(1); - fileSystem.verifyAll(); - }); - test(`Should return interpreter information using PipFile defined in Env variable${testSuffix}`, async () => { - const envPipFile = 'XYZ'; - const env = { - PIPENV_PIPFILE: envPipFile, - }; - const pythonPath = 'one'; - envVarsProvider - .setup((e) => e.getEnvironmentVariables(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({})) - .verifiable(TypeMoq.Times.once()); - currentProcess.setup((c) => c.env).returns(() => env); - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ stdout: pythonPath })); - interpreterHelper - .setup((v) => v.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ version: new SemVer('1.0.0') })); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.never()); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(true)) - .verifiable(); - const environments = await pipEnvService.getInterpreters(resource); - - expect(environments).to.be.lengthOf(1); - fileSystem.verifyAll(); - }); - test("Must use 'python.pipenvPath' setting", async () => { - pipenvPathSetting = 'spam-spam-pipenv-spam-spam'; - const pipenvExe = pipEnvService.executable; - assert.equal(pipenvExe, 'spam-spam-pipenv-spam-spam', 'Failed to identify pipenv.exe'); - }); - - test('Should send telemetry event when calling getInterpreters', async () => { - const sendTelemetryStub = sinon.stub(Telemetry, 'sendTelemetryEvent'); - - await pipEnvService.getInterpreters(resource); - - sinon.assert.calledWith(sendTelemetryStub, EventName.PIPENV_INTERPRETER_DISCOVERY); - sinon.restore(); - }); - }); - - suite('With didTriggerInterpreterSuggestions set to false', () => { - setup(() => { - sinon.stub(pipEnvService, 'didTriggerInterpreterSuggestions').get(() => false); - }); - - teardown(() => { - sinon.restore(); - }); - - test('isRelatedPipEnvironment should exit early', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .verifiable(TypeMoq.Times.never()); - - const result = await pipEnvService.isRelatedPipEnvironment('foo', 'some/python/path'); - - expect(result).to.be.equal(false, 'isRelatedPipEnvironment should return false.'); - processService.verifyAll(); - }); - - test('Executable getter should return an empty string', () => { - const { executable } = pipEnvService; - - expect(executable).to.be.equal('', 'The executable getter should return an empty string.'); - }); - - test('getInterpreters should exit early', async () => { - processService - .setup((p) => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .verifiable(TypeMoq.Times.never()); - - const interpreters = await pipEnvService.getInterpreters(resource); - - expect(interpreters).to.be.lengthOf(0); - processService.verifyAll(); - }); - }); - }); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/progressService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/progressService.unit.test.ts deleted file mode 100644 index b6955775fe1d..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/progressService.unit.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { anything, instance, mock, when } from 'ts-mockito'; -import { Disposable } from 'vscode'; -import { createDeferred } from '../../../../client/common/utils/async'; -import { noop } from '../../../../client/common/utils/misc'; -import { IInterpreterLocatorService } from '../../../../client/interpreter/contracts'; -import { ServiceContainer } from '../../../../client/ioc/container'; -import { InterpreterLocatorProgressService } from '../../../../client/pythonEnvironments/discovery/locators/progressService'; -import { PythonEnvironment } from '../../../../client/pythonEnvironments/info'; -import { sleep } from '../../../core'; - -suite('Interpreters - Locator Progress', () => { - class Locator implements IInterpreterLocatorService { - public locatingCallback?: (e: Promise) => unknown; - - private hasInterpreterValue = true; - - private interpreters: PythonEnvironment[] = []; - - public get hasInterpreters(): Promise { - return Promise.resolve(this.hasInterpreterValue); - } - - public onLocating(listener: (e: Promise) => unknown): Disposable { - this.locatingCallback = listener; - return { dispose: noop }; - } - - public getInterpreters(): Promise { - return Promise.resolve(this.interpreters); - } - - // eslint-disable-next-line class-methods-use-this - public dispose(): void { - noop(); - } - } - - test('Must raise refreshing event', async () => { - const serviceContainer = mock(ServiceContainer); - const locator = new Locator(); - when(serviceContainer.getAll(anything())).thenReturn([locator]); - const progress = new InterpreterLocatorProgressService(instance(serviceContainer), []); - await progress.activate(); - - let refreshingInvoked = false; - progress.onRefreshing(() => { - refreshingInvoked = true; - }); - let refreshedInvoked = false; - progress.onRefreshed(() => { - refreshedInvoked = true; - }); - - const locatingDeferred = createDeferred(); - locator.locatingCallback!.bind(progress)(locatingDeferred.promise); - expect(refreshingInvoked).to.be.equal(true, 'Refreshing Not invoked'); - expect(refreshedInvoked).to.be.equal(false, 'Refreshed invoked'); - }); - test('Must raise refreshed event', async () => { - const serviceContainer = mock(ServiceContainer); - const locator = new Locator(); - when(serviceContainer.getAll(anything())).thenReturn([locator]); - const progress = new InterpreterLocatorProgressService(instance(serviceContainer), []); - await progress.activate(); - - let refreshingInvoked = false; - progress.onRefreshing(() => { - refreshingInvoked = true; - }); - let refreshedInvoked = false; - progress.onRefreshed(() => { - refreshedInvoked = true; - }); - - const locatingDeferred = createDeferred(); - locator.locatingCallback!.bind(progress)(locatingDeferred.promise); - locatingDeferred.resolve(); - - await sleep(10); - expect(refreshingInvoked).to.be.equal(true, 'Refreshing Not invoked'); - expect(refreshedInvoked).to.be.equal(true, 'Refreshed not invoked'); - }); - test('Must raise refreshed event only when all locators have completed', async () => { - const serviceContainer = mock(ServiceContainer); - const locator1 = new Locator(); - const locator2 = new Locator(); - const locator3 = new Locator(); - when(serviceContainer.getAll(anything())).thenReturn([locator1, locator2, locator3]); - const progress = new InterpreterLocatorProgressService(instance(serviceContainer), []); - await progress.activate(); - - let refreshingInvoked = false; - progress.onRefreshing(() => { - refreshingInvoked = true; - }); - let refreshedInvoked = false; - progress.onRefreshed(() => { - refreshedInvoked = true; - }); - - const locatingDeferred1 = createDeferred(); - locator1.locatingCallback!.bind(progress)(locatingDeferred1.promise); - - const locatingDeferred2 = createDeferred(); - locator2.locatingCallback!.bind(progress)(locatingDeferred2.promise); - - const locatingDeferred3 = createDeferred(); - locator3.locatingCallback!.bind(progress)(locatingDeferred3.promise); - - locatingDeferred1.resolve(); - - await sleep(10); - expect(refreshingInvoked).to.be.equal(true, 'Refreshing Not invoked'); - expect(refreshedInvoked).to.be.equal(false, 'Refreshed invoked'); - - locatingDeferred2.resolve(); - - await sleep(10); - expect(refreshedInvoked).to.be.equal(false, 'Refreshed invoked'); - - locatingDeferred3.resolve(); - - await sleep(10); - expect(refreshedInvoked).to.be.equal(true, 'Refreshed not invoked'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/venv.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/venv.unit.test.ts deleted file mode 100644 index 9529be807df9..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/venv.unit.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import { Container } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../client/common/application/types'; -import { PlatformService } from '../../../../client/common/platform/platformService'; -import { IConfigurationService, ICurrentProcess, IPythonSettings } from '../../../../client/common/types'; -import { - IInterpreterAutoSelectionService, - IInterpreterAutoSelectionProxyService, -} from '../../../../client/interpreter/autoSelection/types'; -import { IVirtualEnvironmentManager } from '../../../../client/interpreter/virtualEnvs/types'; -import { ServiceContainer } from '../../../../client/ioc/container'; -import { ServiceManager } from '../../../../client/ioc/serviceManager'; -import { GlobalVirtualEnvironmentsSearchPathProvider } from '../../../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService'; -import { WorkspaceVirtualEnvironmentsSearchPathProvider } from '../../../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService'; -import { MockAutoSelectionService } from '../../../mocks/autoSelector'; - -const untildify: (value: string) => string = require('untildify'); - -suite('Virtual environments', () => { - let serviceManager: ServiceManager; - let serviceContainer: ServiceContainer; - let settings: TypeMoq.IMock; - let config: TypeMoq.IMock; - let workspace: TypeMoq.IMock; - let process: TypeMoq.IMock; - let virtualEnvMgr: TypeMoq.IMock; - - setup(() => { - const cont = new Container(); - serviceManager = new ServiceManager(cont); - serviceContainer = new ServiceContainer(cont); - - settings = TypeMoq.Mock.ofType(); - config = TypeMoq.Mock.ofType(); - workspace = TypeMoq.Mock.ofType(); - process = TypeMoq.Mock.ofType(); - virtualEnvMgr = TypeMoq.Mock.ofType(); - - config.setup((x) => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - - serviceManager.addSingletonInstance(IConfigurationService, config.object); - serviceManager.addSingletonInstance(IWorkspaceService, workspace.object); - serviceManager.addSingletonInstance(ICurrentProcess, process.object); - serviceManager.addSingletonInstance( - IVirtualEnvironmentManager, - virtualEnvMgr.object, - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionService, - MockAutoSelectionService, - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionProxyService, - MockAutoSelectionService, - ); - }); - - test('Global search paths', async () => { - const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer); - - const homedir = os.homedir(); - const folders = ['Envs', 'testpath']; - settings.setup((x) => x.venvFolders).returns(() => folders); - virtualEnvMgr.setup((v) => v.getPyEnvRoot(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); - let paths = await pathProvider.getSearchPaths(); - let expected = ['envs', '.pyenv', '.direnv', '.virtualenvs', ...folders].map((item) => - path.join(homedir, item), - ); - - virtualEnvMgr.verifyAll(); - expect(paths).to.deep.equal(expected, 'Global search folder list is incorrect.'); - - virtualEnvMgr.reset(); - virtualEnvMgr.setup((v) => v.getPyEnvRoot(TypeMoq.It.isAny())).returns(() => Promise.resolve('pyenv_path')); - paths = await pathProvider.getSearchPaths(); - - virtualEnvMgr.verifyAll(); - expected = expected.concat(['pyenv_path', path.join('pyenv_path', 'versions')]); - expect(paths).to.deep.equal(expected, 'pyenv path not resolved correctly.'); - }); - - test('Global search paths with duplicates', async () => { - const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer); - - const folders = ['.virtualenvs', '.direnv']; - settings.setup((x) => x.venvFolders).returns(() => folders); - const paths = await pathProvider.getSearchPaths(); - - expect([...new Set(paths)]).to.deep.equal( - paths, - 'Duplicates are not removed from the list of global search paths', - ); - }); - - test('Global search paths with tilde path in the WORKON_HOME environment variable', async () => { - const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer); - - const homedir = os.homedir(); - const workonFolder = path.join('~', '.workonFolder'); - process.setup((p) => p.env).returns(() => ({ WORKON_HOME: workonFolder })); - settings.setup((x) => x.venvFolders).returns(() => []); - - const paths = await pathProvider.getSearchPaths(); - const expected = ['envs', '.pyenv', '.direnv', '.virtualenvs'].map((item) => path.join(homedir, item)); - expected.push(untildify(workonFolder)); - - expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.'); - }); - - test('Global search paths with absolute path in the WORKON_HOME environment variable', async () => { - const pathProvider = new GlobalVirtualEnvironmentsSearchPathProvider(serviceContainer); - - const homedir = os.homedir(); - const workonFolder = path.join('path', 'to', '.workonFolder'); - process.setup((p) => p.env).returns(() => ({ WORKON_HOME: workonFolder })); - settings.setup((x) => x.venvFolders).returns(() => []); - - const paths = await pathProvider.getSearchPaths(); - const expected = ['envs', '.pyenv', '.direnv', '.virtualenvs'].map((item) => path.join(homedir, item)); - expected.push(workonFolder); - - expect(paths).to.deep.equal(expected, 'WORKON_HOME environment variable not read.'); - }); - - test('Workspace search paths', async () => { - settings.setup((x) => x.venvPath).returns(() => path.join('~', 'foo')); - - const wsRoot = TypeMoq.Mock.ofType(); - wsRoot.setup((x) => x.uri).returns(() => Uri.file('root')); - - const folder1 = TypeMoq.Mock.ofType(); - folder1.setup((x) => x.uri).returns(() => Uri.file('dir1')); - - workspace.setup((x) => x.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => wsRoot.object); - workspace.setup((x) => x.workspaceFolders).returns(() => [wsRoot.object, folder1.object]); - - const pathProvider = new WorkspaceVirtualEnvironmentsSearchPathProvider(serviceContainer); - const paths = await pathProvider.getSearchPaths(Uri.file('')); - - const homedir = os.homedir(); - const isWindows = new PlatformService(); - const fixCase = (item: string) => (isWindows ? item.toUpperCase() : item); - const expected = [path.join(homedir, 'foo'), 'root', path.join('root', '.direnv')] - .map((item) => Uri.file(item).fsPath) - .map(fixCase); - expect(paths.map(fixCase)).to.deep.equal(expected, 'Workspace venv folder search list does not match.'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts deleted file mode 100644 index 4570d3feb143..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/windowsRegistryService.unit.test.ts +++ /dev/null @@ -1,949 +0,0 @@ -import * as assert from 'assert'; -import * as fsextra from 'fs-extra'; -import * as path from 'path'; -import * as sinon from 'sinon'; -import * as TypeMoq from 'typemoq'; -import { IFileSystem, IPlatformService, RegistryHive } from '../../../../client/common/platform/types'; -import { IPathUtils, IPersistentStateFactory } from '../../../../client/common/types'; -import { Architecture } from '../../../../client/common/utils/platform'; -import { IInterpreterHelper } from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { WindowsRegistryService } from '../../../../client/pythonEnvironments/discovery/locators/services/windowsRegistryService'; -import { EnvironmentType } from '../../../../client/pythonEnvironments/info'; -import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants'; -import { MockRegistry, MockState } from '../../../interpreters/mocks'; -import * as WindowsInterpreter from '../../../../client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; - -const environmentsPath = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'test', 'pythonFiles', 'environments'); - -suite('Interpreters from Windows Registry (unit)', () => { - let serviceContainer: TypeMoq.IMock; - let interpreterHelper: TypeMoq.IMock; - let platformService: TypeMoq.IMock; - let fs: TypeMoq.IMock; - let isRestrictedWindowsInterpreterStub: sinon.SinonStub; - let isWindowsStoreInterpreterStub: sinon.SinonStub; - - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - const stateFactory = TypeMoq.Mock.ofType(); - interpreterHelper = TypeMoq.Mock.ofType(); - const pathUtils = TypeMoq.Mock.ofType(); - platformService = TypeMoq.Mock.ofType(); - fs = TypeMoq.Mock.ofType(); - isRestrictedWindowsInterpreterStub = sinon.stub(WindowsInterpreter, 'isRestrictedWindowsStoreInterpreterPath'); - isWindowsStoreInterpreterStub = sinon.stub(WindowsInterpreter, 'isWindowsStoreInterpreter'); - - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IPersistentStateFactory))) - .returns(() => stateFactory.object); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterHelper))) - .returns(() => interpreterHelper.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IPathUtils))).returns(() => pathUtils.object); - serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fs.object); - pathUtils - .setup((p) => p.basename(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((p: string) => p.split(/[\\,\/]/).reverse()[0]); - // So effectively these are functional tests... - fs.setup((f) => f.fileExists(TypeMoq.It.isAny())).returns((filename) => fsextra.pathExists(filename)); - const state = new MockState(undefined); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .returns(() => Promise.resolve({} as any)); - stateFactory - .setup((s) => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => state); - }); - - teardown(() => { - sinon.restore(); - }); - - function setup64Bit(is64Bit: boolean) { - platformService.setup((ps) => ps.is64bit).returns(() => is64Bit); - return platformService.object; - } - test('Must return an empty list (x86)', async () => { - const registry = new MockRegistry([], []); - const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); - platformService.setup((p) => p.isWindows).returns(() => true); - - 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, setup64Bit(true), serviceContainer.object); - platformService.setup((p) => p.isWindows).returns(() => true); - - const interpreters = await winRegistry.getInterpreters(); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return a single entry', async () => { - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One'], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\Tag1'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'Display Name for Company One', - name: 'DisplayName', - }, - { - key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - { - key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1', 'one.exe'), - name: 'ExecutablePath', - }, - { - key: '\\Software\\Python\\Company One\\Tag1', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: '9.9.9.final', - name: 'SysVersion', - }, - { - 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, setup64Bit(false), serviceContainer.object); - - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - platformService.setup((p) => p.isWindows).returns(() => true); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal( - interpreters[0].path, - path.join(environmentsPath, 'path1', 'one.exe'), - 'Incorrect executable path', - ); - assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); - }); - test('Must default names for PythonCore and exe', async () => { - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\PythonCore'], - }, - { - key: '\\Software\\Python\\PythonCore', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\PythonCore\\9.9.9-final'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\PythonCore\\9.9.9-final\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); - - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - platformService.setup((p) => p.isWindows).returns(() => true); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Python Software Foundation', 'Incorrect company name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); - }); - test("Must ignore company 'PyLauncher'", async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\PyLauncher'], - }, - { - key: '\\Software\\Python\\PythonCore', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\PyLauncher\\Tag1'], - }, - ]; - const registryValues = [ - { - 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, setup64Bit(false), serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return a single entry and when registry contains only the InstallPath', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One'], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\9.9.9-final'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\Company One\\9.9.9-final\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Company One', 'Incorrect company name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version!.raw, '9.9.9-final', 'Incorrect version'); - assert.equal(interpreters[0].envType, EnvironmentType.Unknown, 'Incorrect type'); - }); - test('Must return a single entry with a type of WindowsStore', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One'], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\9.9.9-final'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\Company One\\9.9.9-final\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - isRestrictedWindowsInterpreterStub.returns(false); - isWindowsStoreInterpreterStub.returns(true); - const expectedPythonPath = path.join(environmentsPath, 'path1', 'python.exe'); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].envType, EnvironmentType.WindowsStore, 'Incorrect type'); - sinon.assert.calledWith(isRestrictedWindowsInterpreterStub, expectedPythonPath); - sinon.assert.calledWith(isWindowsStoreInterpreterStub, expectedPythonPath); - }); - test('Must not return any interpreters (must ignore internal windows store intrepreters)', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One'], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\9.9.9-final'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\Company One\\9.9.9-final\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - isRestrictedWindowsInterpreterStub.returns(true); - const expectedPythonPath = path.join(environmentsPath, 'path1', 'python.exe'); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - sinon.assert.calledWith(isRestrictedWindowsInterpreterStub, expectedPythonPath); - }); - test('Must return multiple entries', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company One', - '\\Software\\Python\\Company Two', - '\\Software\\Python\\Company Three', - ], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\2.0.0'], - }, - { - key: '\\Software\\Python\\Company Two', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company Two\\3.0.0', - '\\Software\\Python\\Company Two\\4.0.0', - '\\Software\\Python\\Company Two\\5.0.0', - ], - }, - { - key: '\\Software\\Python\\Company Three', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Three\\6.0.0'], - }, - { - key: '\\Software\\Python', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['7.0.0'], - }, - { - key: '\\Software\\Python\\Company A', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['8.0.0'], - }, - ]; - const registryValues = [ - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'Display Name for Company One', - name: 'DisplayName', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1', 'python.exe'), - name: 'ExecutablePath', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path2'), - name: 'SysVersion', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag1', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path2'), - }, - { - key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path2', 'python.exe'), - name: 'ExecutablePath', - }, - - { - key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path3'), - }, - { - key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: '3.0.0', - name: 'SysVersion', - }, - - { - key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - { - key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag B', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company Two\\5.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'scipy'), - }, - - { - key: '\\Software\\Python\\Company Three\\6.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - - { - key: '\\Software\\Python\\Company A\\8.0.0\\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, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 4, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version!.raw, '1.0.0', 'Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[1].version!.raw, '2.0.0', 'Incorrect version'); - - assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal( - interpreters[2].path, - path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), - 'Incorrect path', - ); - assert.equal(interpreters[2].version!.raw, '4.0.0', 'Incorrect version'); - - assert.equal(interpreters[3].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[3].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal( - interpreters[3].path, - path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), - 'Incorrect path', - ); - assert.equal(interpreters[3].version!.raw, '5.0.0', 'Incorrect version'); - }); - test('Must return multiple entries excluding the invalid registry items and duplicate paths', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company One', - '\\Software\\Python\\Company Two', - '\\Software\\Python\\Company Three', - '\\Software\\Python\\Company Four', - '\\Software\\Python\\Company Five', - 'Missing Tag', - ], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\2.0.0'], - }, - { - key: '\\Software\\Python\\Company Two', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company Two\\3.0.0', - '\\Software\\Python\\Company Two\\4.0.0', - '\\Software\\Python\\Company Two\\5.0.0', - ], - }, - { - key: '\\Software\\Python\\Company Three', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Three\\6.0.0'], - }, - { - key: '\\Software\\Python\\Company Four', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Four\\7.0.0'], - }, - { - key: '\\Software\\Python\\Company Five', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Five\\8.0.0'], - }, - { - key: '\\Software\\Python', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['9.0.0'], - }, - { - key: '\\Software\\Python\\Company A', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['10.0.0'], - }, - ]; - const registryValues: { - key: string; - hive: RegistryHive; - arch?: Architecture; - value: string; - name?: string; - }[] = [ - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'Display Name for Company One', - name: 'DisplayName', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), - name: 'ExecutablePath', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: '1.0.0-final', - name: 'SysVersion', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag1', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'scipy'), - }, - { - key: '\\Software\\Python\\Company One\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), - name: 'ExecutablePath', - }, - - { - key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path1'), - }, - { - key: '\\Software\\Python\\Company Two\\3.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: '3.0.0', - name: 'SysVersion', - }, - - { - key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path2'), - }, - { - key: '\\Software\\Python\\Company Two\\4.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag B', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company Two\\5.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - - { - key: '\\Software\\Python\\Company Five\\8.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: undefined, - }, - - { - key: '\\Software\\Python\\Company Three\\6.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - - { - key: '\\Software\\Python\\Company A\\10.0.0\\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, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 4, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal( - interpreters[0].path, - path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), - 'Incorrect path', - ); - assert.equal(interpreters[0].version!.raw, '1.0.0', 'Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal( - interpreters[1].path, - path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), - 'Incorrect path', - ); - assert.equal(interpreters[1].version!.raw, '2.0.0', 'Incorrect version'); - - assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal(interpreters[2].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[2].version!.raw, '3.0.0', 'Incorrect version'); - - assert.equal(interpreters[3].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[3].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal(interpreters[3].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[3].version!.raw, '4.0.0', 'Incorrect version'); - }); - test('Must return multiple entries excluding the invalid registry items and nonexistent paths', async () => { - platformService.setup((p) => p.isWindows).returns(() => true); - const registryKeys = [ - { - key: '\\Software\\Python', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company One', - '\\Software\\Python\\Company Two', - '\\Software\\Python\\Company Three', - '\\Software\\Python\\Company Four', - '\\Software\\Python\\Company Five', - 'Missing Tag', - ], - }, - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company One\\1.0.0', '\\Software\\Python\\Company One\\Tag2'], - }, - { - key: '\\Software\\Python\\Company Two', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: [ - '\\Software\\Python\\Company Two\\Tag A', - '\\Software\\Python\\Company Two\\2.0.0', - '\\Software\\Python\\Company Two\\Tag C', - ], - }, - { - key: '\\Software\\Python\\Company Three', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Three\\Tag !'], - }, - { - key: '\\Software\\Python\\Company Four', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Four\\Four !'], - }, - { - key: '\\Software\\Python\\Company Five', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - values: ['\\Software\\Python\\Company Five\\Five !'], - }, - { - key: '\\Software\\Python', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['A'], - }, - { - key: '\\Software\\Python\\Company A', - hive: RegistryHive.HKLM, - arch: Architecture.x86, - values: ['Another Tag'], - }, - ]; - const registryValues: { - key: string; - hive: RegistryHive; - arch?: Architecture; - value: string; - name?: string; - }[] = [ - { - key: '\\Software\\Python\\Company One', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'Display Name for Company One', - name: 'DisplayName', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy'), - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), - name: 'ExecutablePath', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'Version.Tag1', - name: 'SysVersion', - }, - { - key: '\\Software\\Python\\Company One\\1.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag1', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy'), - }, - { - key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy', 'python.exe'), - name: 'ExecutablePath', - }, - - { - key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'non-existent-path'), - }, - { - key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: '2.0.0', - name: 'SysVersion', - }, - - { - key: '\\Software\\Python\\Company Two\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'path2'), - }, - { - key: '\\Software\\Python\\Company Two\\2.0.0\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: 'DisplayName.Tag B', - name: 'DisplayName', - }, - - { - key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy'), - }, - - { - key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: undefined, - }, - - { - key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', - hive: RegistryHive.HKCU, - arch: Architecture.x86, - value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy'), - }, - - { - 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, setup64Bit(false), serviceContainer.object); - interpreterHelper.reset(); - interpreterHelper - .setup((h) => h.getInterpreterInformation(TypeMoq.It.isAny())) - .returns(() => Promise.resolve({ architecture: Architecture.x86 })); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 2, 'Incorrect number of entries'); - - assert.equal(interpreters[0].architecture, Architecture.x86, '1. Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', '1. Incorrect company name'); - assert.equal( - interpreters[0].path, - path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), - '1. Incorrect path', - ); - assert.equal(interpreters[0].version!.raw, '1.0.0', '1. Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, '2. Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Company Two', '2. Incorrect company name'); - assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), '2. Incorrect path'); - assert.equal(interpreters[1].version!.raw, '2.0.0', '2. Incorrect version'); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts deleted file mode 100644 index de5a09a0ebaf..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/windowsStoreInterpreter.unit.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { - isWindowsStoreInterpreter, - isRestrictedWindowsStoreInterpreterPath, -} from '../../../../client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; - -suite('Interpreters - Windows Store Interpreter', () => { - const windowsStoreInterpreters = [ - '\\\\Program Files\\WindowsApps\\Something\\Python.exe', - '..\\Program Files\\WindowsApps\\Something\\Python.exe', - '..\\one\\Program Files\\WindowsApps\\Something\\Python.exe', - 'C:\\Program Files\\WindowsApps\\Something\\Python.exe', - 'C:\\Program Files\\WindowsApps\\Python.exe', - 'C:\\Microsoft\\WindowsApps\\Something\\Python.exe', - 'C:\\Microsoft\\WindowsApps\\Python.exe', - 'C:\\Microsoft\\WindowsApps\\PythonSoftwareFoundation\\Python.exe', - 'C:\\microsoft\\WindowsApps\\PythonSoftwareFoundation\\Something\\Python.exe', - ]; - - for (const interpreter of windowsStoreInterpreters) { - test(`${interpreter} must be identified as a Windows Store interpreter`, async () => { - expect(await isWindowsStoreInterpreter(interpreter)).to.equal(true, 'Must be true'); - }); - - test(`${interpreter.toLowerCase()} must be identified as a Windows Store interpreter (lower case)`, async () => { - expect(await isWindowsStoreInterpreter(interpreter.toLowerCase())).to.equal(true, 'Must be true'); - expect(await isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal(true, 'Must be true'); - }); - - const otherDrive = `D${interpreter.substring(1)}`; - test(`${otherDrive} must be identified as a Windows Store interpreter (ignoring driver letter)`, async () => { - expect(await isWindowsStoreInterpreter(otherDrive)).to.equal(true, 'Must be true'); - }); - - const ignorePathSeparator = interpreter.replace(/\\/g, '/'); - test(`${ignorePathSeparator} must be identified as a Windows Store interpreter (ignoring path separator)`, async () => { - expect(await isWindowsStoreInterpreter(ignorePathSeparator)).to.equal(true, 'Must be true'); - }); - } - - const nonWindowsStoreInterpreters = [ - '..\\Program Filess\\WindowsApps\\Something\\Python.exe', - 'C:\\Program Filess\\WindowsApps\\Something\\Python.exe', - 'C:\\Program Files\\WindowsAppss\\Python.exe', - 'C:\\Microsofts\\WindowsApps\\Something\\Python.exe', - 'C:\\Microsoft\\WindowsAppss\\Python.exe', - 'C:\\Microsofts\\WindowsApps\\PythonSoftwareFoundation\\Python.exe', - 'C:\\microsoft\\WindowsAppss\\PythonSoftwareFoundation\\Something\\Python.exe', - 'C:\\Python\\python.exe', - 'C:\\Program Files\\Python\\python.exe', - 'C:\\Program Files\\Microsoft\\Python\\python.exe', - '..\\apps\\Python.exe', - 'C:\\Apps\\Python.exe', - ]; - for (const interpreter of nonWindowsStoreInterpreters) { - test(`${interpreter} must not be identified as a Windows Store interpreter`, async () => { - const ignorePathSeparator = interpreter.replace(/\\/g, '/'); - - expect(isRestrictedWindowsStoreInterpreterPath(interpreter)).to.equal(false, 'Must be false'); - expect(isRestrictedWindowsStoreInterpreterPath(ignorePathSeparator)).to.equal(false, 'Must be false'); - - expect(await isWindowsStoreInterpreter(interpreter)).to.equal(false, 'Must be false'); - expect(await isWindowsStoreInterpreter(ignorePathSeparator)).to.equal(false, 'Must be false'); - - expect(isRestrictedWindowsStoreInterpreterPath(interpreter.toLowerCase())).to.equal(false, 'Must be false'); - expect(await isWindowsStoreInterpreter(interpreter.toUpperCase())).to.equal(false, 'Must be false'); - expect(await isWindowsStoreInterpreter(`D${interpreter.substring(1)}`)).to.equal(false, 'Must be false'); - }); - } - const windowsStoreHiddenInterpreters = [ - 'C:\\Program Files\\WindowsApps\\Something\\Python.exe', - 'C:\\Program Files\\WindowsApps\\Python.exe', - 'C:\\Microsoft\\WindowsApps\\PythonSoftwareFoundation\\Python.exe', - 'C:\\microsoft\\WindowsApps\\PythonSoftwareFoundation\\Something\\Python.exe', - ]; - for (const interpreter of windowsStoreHiddenInterpreters) { - test(`${interpreter} must be identified as a Windows Store (hidden) interpreter`, () => { - expect(isRestrictedWindowsStoreInterpreterPath(interpreter)).to.equal(true, 'Must be true'); - }); - - test(`${interpreter.toLowerCase()} must be identified as a Windows Store (hidden) interpreter (ignoring case)`, () => { - expect(isRestrictedWindowsStoreInterpreterPath(interpreter.toLowerCase())).to.equal(true, 'Must be true'); - expect(isRestrictedWindowsStoreInterpreterPath(interpreter.toUpperCase())).to.equal(true, 'Must be true'); - }); - - const otherDrive = `D${interpreter.substring(1)}`; - test(`${otherDrive} must be identified as a Windows Store (hidden) interpreter (ignoring driver letter)`, () => { - expect(isRestrictedWindowsStoreInterpreterPath(otherDrive)).to.equal(true, 'Must be true'); - }); - } - const nonWindowsStoreHiddenInterpreters = [ - 'C:\\Microsofts\\WindowsApps\\Something\\Python.exe', - 'C:\\Microsoft\\WindowsAppss\\Python.exe', - ]; - for (const interpreter of nonWindowsStoreHiddenInterpreters) { - test(`${interpreter} must not be identified as a Windows Store (hidden) interpreter`, () => { - expect(isRestrictedWindowsStoreInterpreterPath(interpreter)).to.equal(false, 'Must be true'); - }); - } -}); diff --git a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.test.ts b/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.test.ts deleted file mode 100644 index 0746e5d6bb1f..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { exec } from 'child_process'; -import * as path from 'path'; -import { promisify } from 'util'; -import { Uri } from 'vscode'; -import '../../../../client/common/extensions'; -import { createDeferredFromPromise, Deferred } from '../../../../client/common/utils/async'; -import { StopWatch } from '../../../../client/common/utils/stopWatch'; -import { - IInterpreterLocatorService, - IInterpreterWatcherBuilder, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; -import { WorkspaceVirtualEnvWatcherService } from '../../../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService'; -import { IS_CI_SERVER } from '../../../ciConstants'; -import { - deleteFiles, - getOSType, - isPythonVersionInProcess, - OSType, - PYTHON_PATH, - rootWorkspaceUri, - waitForCondition, -} from '../../../common'; -import { IS_MULTI_ROOT_TEST } from '../../../constants'; -import { sleep } from '../../../core'; -import { initialize, multirootPath } from '../../../initialize'; - -const execAsync = promisify(exec); -async function run(argv: string[], cwd: string) { - const cmdline = argv.join(' '); - const { stderr } = await execAsync(cmdline, { - cwd, - }); - if (stderr && stderr.length > 0) { - throw Error(stderr); - } -} - -class Venvs { - constructor(private readonly cwd: string, private readonly prefix = '.venv-') {} - - public async create(name: string): Promise { - const venvRoot = this.resolve(name); - const argv = [PYTHON_PATH.fileToCommandArgument(), '-m', 'venv', venvRoot]; - try { - await run(argv, this.cwd); - } catch (err) { - throw new Error(`Failed to create Env ${path.basename(venvRoot)}, ${PYTHON_PATH}, Error: ${err}`); - } - return venvRoot; - } - - public async cleanUp() { - const globPattern = path.join(this.cwd, `${this.prefix}*`); - await deleteFiles(globPattern); - } - - private getID(name: string): string { - // Ensure env is random to avoid conflicts in tests (currupting test data). - const now = new Date().getTime().toString(); - return `${this.prefix}${name}${now}`; - } - - private resolve(name: string): string { - const id = this.getID(name); - return path.join(this.cwd, id); - } -} - -const baseTimeoutMs = 30_000; -const timeoutMs = IS_CI_SERVER ? baseTimeoutMs * 4 : baseTimeoutMs; -suite('Interpreters - Workspace VirtualEnv Service', function () { - suiteSetup(async function () { - // TODO: https://github.com/microsoft/vscode-python/issues/13649 - // These tests have been disabled due to flakiness. It's likely - // that we will replace them while refactoring the locators - // rather than fix these tests. - return this.skip(); - }); - - this.timeout(timeoutMs); - this.retries(0); - - const workspaceUri = IS_MULTI_ROOT_TEST ? Uri.file(path.join(multirootPath, 'workspace3')) : rootWorkspaceUri!; - // "workspace4 does not exist. - const workspace4 = Uri.file(path.join(multirootPath, 'workspace4')); - const venvs = new Venvs(workspaceUri.fsPath); - - let serviceContainer: IServiceContainer; - let locator: IInterpreterLocatorService; - - async function manuallyTriggerFSWatcher(deferred: Deferred) { - // Monitoring files on virtualized environments can be finicky... - // Lets trigger the fs watcher manually for the tests. - const stopWatch = new StopWatch(); - const builder = serviceContainer.get(IInterpreterWatcherBuilder); - const watcher = (await builder.getWorkspaceVirtualEnvInterpreterWatcher( - workspaceUri, - )) as WorkspaceVirtualEnvWatcherService; - const binDir = getOSType() === OSType.Windows ? 'Scripts' : 'bin'; - const executable = getOSType() === OSType.Windows ? 'python.exe' : 'python'; - while (!deferred.completed && stopWatch.elapsedTime < timeoutMs - 10_000) { - const pythonPath = path.join(workspaceUri.fsPath, binDir, executable); - watcher.createHandler(Uri.file(pythonPath)).ignoreErrors(); - await sleep(1000); - } - } - async function waitForInterpreterToBeDetected(venvRoot: string) { - const envNameToLookFor = path.basename(venvRoot); - const predicate = async () => { - const items = await locator.getInterpreters(workspaceUri); - return items.some((item) => item.envName === envNameToLookFor); - }; - const promise = waitForCondition( - predicate, - timeoutMs, - `${envNameToLookFor}, Environment not detected in the workspace ${workspaceUri.fsPath}`, - ); - const deferred = createDeferredFromPromise(promise); - manuallyTriggerFSWatcher(deferred).ignoreErrors(); - await deferred.promise; - } - async function createVirtualEnvironment(envSuffix: string) { - return venvs.create(envSuffix); - } - - suiteSetup(async function () { - // skip for Python < 3, no venv support - if (await isPythonVersionInProcess(undefined, '2')) { - return this.skip(); - } - - serviceContainer = (await initialize()).serviceContainer; - locator = serviceContainer.get( - IInterpreterLocatorService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - // This test is required, we need to wait for interpreter listing completes, - // before proceeding with other tests. - await venvs.cleanUp(); - await locator.getInterpreters(workspaceUri); - - return undefined; - }); - - suiteTeardown(async () => venvs.cleanUp()); - teardown(async () => venvs.cleanUp()); - - test('Detect Virtual Environment', async () => { - const envName = await createVirtualEnvironment('one'); - await waitForInterpreterToBeDetected(envName); - }); - - test('Detect a new Virtual Environment', async () => { - const env1 = await createVirtualEnvironment('first'); - await waitForInterpreterToBeDetected(env1); - - // Ensure second environment in our workspace folder is detected when created. - const env2 = await createVirtualEnvironment('second'); - await waitForInterpreterToBeDetected(env2); - }); - - test('Detect a new Virtual Environment, and other workspace folder must not be affected (multiroot)', async function () { - if (!IS_MULTI_ROOT_TEST) { - return this.skip(); - } - // There should be nothing in workspacec4. - let items4 = await locator.getInterpreters(workspace4); - expect(items4).to.be.lengthOf(0); - - const [env1, env2] = await Promise.all([ - createVirtualEnvironment('first3'), - createVirtualEnvironment('second3'), - ]); - await Promise.all([waitForInterpreterToBeDetected(env1), waitForInterpreterToBeDetected(env2)]); - - // Workspace4 should still not have any interpreters. - items4 = await locator.getInterpreters(workspace4); - expect(items4).to.be.lengthOf(0); - - return undefined; - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.unit.test.ts deleted file mode 100644 index bd15e421b68d..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvService.unit.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; -import { Uri } from 'vscode'; -import { IInterpreterWatcher } from '../../../../client/interpreter/contracts'; -import { ServiceContainer } from '../../../../client/ioc/container'; -import { InterpreterWatcherBuilder } from '../../../../client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder'; -import { WorkspaceVirtualEnvService } from '../../../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService'; - -suite('Interpreters - Workspace VirtualEnv Service', () => { - test('Get list of watchers', async () => { - const serviceContainer = mock(ServiceContainer); - const builder = mock(InterpreterWatcherBuilder); - const locator = new (class extends WorkspaceVirtualEnvService { - public async getInterpreterWatchers(resource: Uri | undefined): Promise { - return super.getInterpreterWatchers(resource); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - })(undefined as any, instance(serviceContainer), instance(builder)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const watchers = 1 as any; - when(builder.getWorkspaceVirtualEnvInterpreterWatcher(anything())).thenResolve(watchers); - - const items = await locator.getInterpreterWatchers(undefined); - - expect(items).to.deep.equal([watchers]); - verify(builder.getWorkspaceVirtualEnvInterpreterWatcher(anything())).once(); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvWatcherService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvWatcherService.unit.test.ts deleted file mode 100644 index 12991315e8e0..000000000000 --- a/src/test/pythonEnvironments/discovery/locators/workspaceVirtualEnvWatcherService.unit.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable max-classes-per-file */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; -import { Disposable, FileSystemWatcher, Uri, WorkspaceFolder } from 'vscode'; -import { WorkspaceService } from '../../../../client/common/application/workspace'; -import { isUnitTestExecution } from '../../../../client/common/constants'; -import { PlatformService } from '../../../../client/common/platform/platformService'; -import { PythonExecutionFactory } from '../../../../client/common/process/pythonExecutionFactory'; -import { sleep } from '../../../../client/common/utils/async'; -import { noop } from '../../../../client/common/utils/misc'; -import { OSType } from '../../../../client/common/utils/platform'; -import { WorkspaceVirtualEnvWatcherService } from '../../../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService'; - -suite('Interpreters - Workspace VirtualEnv Watcher Service', () => { - let disposables: Disposable[] = []; - setup(function () { - if (!isUnitTestExecution()) { - return this.skip(); - } - - return undefined; - }); - teardown(() => { - disposables.forEach((d) => { - try { - d.dispose(); - } catch { - noop(); - } - }); - disposables = []; - }); - - async function checkForFileChanges(os: OSType, resource: Uri | undefined, hasWorkspaceFolder: boolean) { - const workspaceService = mock(WorkspaceService); - const platformService = mock(PlatformService); - const execFactory = mock(PythonExecutionFactory); - const watcher = new WorkspaceVirtualEnvWatcherService( - [], - instance(workspaceService), - instance(platformService), - instance(execFactory), - ); - - when(platformService.isWindows).thenReturn(os === OSType.Windows); - when(platformService.isLinux).thenReturn(os === OSType.Linux); - when(platformService.isMac).thenReturn(os === OSType.OSX); - - class FSWatcher { - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-explicit-any - public onDidCreate(_listener: (e: Uri) => any, _thisArgs?: any, _disposables?: Disposable[]): Disposable { - return { dispose: noop }; - } - } - - const workspaceFolder: WorkspaceFolder = { name: 'one', index: 1, uri: Uri.file(path.join('root', 'dev')) }; - if (!hasWorkspaceFolder || !resource) { - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(undefined); - } else { - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - } - - const fsWatcher = mock(FSWatcher); - when(workspaceService.createFileSystemWatcher(anything())).thenReturn( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - instance((fsWatcher as any) as FileSystemWatcher), - ); - - await watcher.register(resource); - - verify(workspaceService.createFileSystemWatcher(anything())).twice(); - verify(fsWatcher.onDidCreate(anything(), anything(), anything())).twice(); - } - for (const uri of [undefined, Uri.file('abc')]) { - for (const hasWorkspaceFolder of [true, false]) { - const uriSuffix = uri - ? ` (with resource & ${hasWorkspaceFolder ? 'with' : 'without'} workspace folder)` - : ''; - test(`Register for file changes on windows ${uriSuffix}`, async () => { - await checkForFileChanges(OSType.Windows, uri, hasWorkspaceFolder); - }); - test(`Register for file changes on Mac ${uriSuffix}`, async () => { - await checkForFileChanges(OSType.OSX, uri, hasWorkspaceFolder); - }); - test(`Register for file changes on Linux ${uriSuffix}`, async () => { - await checkForFileChanges(OSType.Linux, uri, hasWorkspaceFolder); - }); - } - } - async function ensureFileChanesAreHandled(os: OSType) { - const workspaceService = mock(WorkspaceService); - const platformService = mock(PlatformService); - const execFactory = mock(PythonExecutionFactory); - const watcher = new WorkspaceVirtualEnvWatcherService( - disposables, - instance(workspaceService), - instance(platformService), - instance(execFactory), - ); - - when(platformService.isWindows).thenReturn(os === OSType.Windows); - when(platformService.isLinux).thenReturn(os === OSType.Linux); - when(platformService.isMac).thenReturn(os === OSType.OSX); - - class FSWatcher { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private listener?: (e: Uri) => any; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public onDidCreate(listener: (e: Uri) => any, _thisArgs?: any, _disposables?: Disposable[]): Disposable { - this.listener = listener; - return { dispose: noop }; - } - - public invokeListener(e: Uri) { - this.listener!(e); - } - } - const fsWatcher = new FSWatcher(); - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(undefined); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - when(workspaceService.createFileSystemWatcher(anything())).thenReturn((fsWatcher as any) as FileSystemWatcher); - await watcher.register(undefined); - let invoked = false; - watcher.onDidCreate(() => { - invoked = true; - }, watcher); - - fsWatcher.invokeListener(Uri.file('')); - // We need this sleep, as we have a debounce (so lets wait). - await sleep(10); - - expect(invoked).to.be.equal(true, 'invalid'); - } - test('Check file change handler on Windows', async () => { - await ensureFileChanesAreHandled(OSType.Windows); - }); - test('Check file change handler on Mac', async () => { - await ensureFileChanesAreHandled(OSType.OSX); - }); - test('Check file change handler on Linux', async () => { - await ensureFileChanesAreHandled(OSType.Linux); - }); -}); diff --git a/src/test/pythonEnvironments/discovery/subenv.unit.test.ts b/src/test/pythonEnvironments/discovery/subenv.unit.test.ts deleted file mode 100644 index 47f648079ba8..000000000000 --- a/src/test/pythonEnvironments/discovery/subenv.unit.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import * as TypeMoq from 'typemoq'; -import * as sut from '../../../client/pythonEnvironments/discovery/subenv'; -import { EnvironmentType } from '../../../client/pythonEnvironments/info'; - -suite('getName()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); - -suite('getType()', () => { - interface IFinders { - venv(python: string): Promise; - pyenv(python: string): Promise; - pipenv(python: string): Promise; - virtualenv(python: string): Promise; - } - let finders: TypeMoq.IMock; - setup(() => { - finders = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); - }); - function verifyAll() { - finders.verifyAll(); - } - - test('detects the first type', async () => { - const python = 'x/y/z/bin/python'; - finders - .setup((f) => f.venv(python)) - // found - .returns(() => Promise.resolve(EnvironmentType.Venv)); - - const result = await sut.getType(python, [ - (p: string) => finders.object.venv(p), - (p: string) => finders.object.pyenv(p), - (p: string) => finders.object.pipenv(p), - (p: string) => finders.object.virtualenv(p), - ]); - - expect(result).to.equal(EnvironmentType.Venv, 'broken'); - verifyAll(); - }); - - test('detects the second type', async () => { - const python = 'x/y/z/bin/python'; - finders - .setup((f) => f.venv(python)) - // not found - .returns(() => Promise.resolve(undefined)); - finders - .setup((f) => f.pyenv(python)) - // found - .returns(() => Promise.resolve(EnvironmentType.Pyenv)); - - const result = await sut.getType(python, [ - (p: string) => finders.object.venv(p), - (p: string) => finders.object.pyenv(p), - (p: string) => finders.object.pipenv(p), - (p: string) => finders.object.virtualenv(p), - ]); - - expect(result).to.equal(EnvironmentType.Pyenv, 'broken'); - verifyAll(); - }); - - test('does not detect the type', async () => { - const python = 'x/y/z/bin/python'; - finders - .setup((f) => f.venv(python)) - // not found - .returns(() => Promise.resolve(undefined)); - finders - .setup((f) => f.pyenv(python)) - // not found - .returns(() => Promise.resolve(undefined)); - finders - .setup((f) => f.pipenv(python)) - // not found - .returns(() => Promise.resolve(undefined)); - finders - .setup((f) => f.virtualenv(python)) - // not found - .returns(() => Promise.resolve(undefined)); - - const result = await sut.getType(python, [ - (p: string) => finders.object.venv(p), - (p: string) => finders.object.pyenv(p), - (p: string) => finders.object.pipenv(p), - (p: string) => finders.object.virtualenv(p), - ]); - - expect(result).to.equal(undefined, 'broken'); - verifyAll(); - }); -}); - -suite('getNameFinders()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); - -suite('getTypeFinders()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); - -suite('getVenvTypeFinder()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); - -suite('getVirtualenvTypeFinder()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); - -suite('getPipenvTypeFinder()', () => { - // We will pull tests over from src/test/interpreters/virtualEnvs/index.unit.test.ts at some point. -}); diff --git a/src/test/pythonEnvironments/legacyIOC.ts b/src/test/pythonEnvironments/legacyIOC.ts index 8e0cf6e34816..c521569c77d8 100644 --- a/src/test/pythonEnvironments/legacyIOC.ts +++ b/src/test/pythonEnvironments/legacyIOC.ts @@ -5,7 +5,7 @@ import { instance, mock } from 'ts-mockito'; import { IServiceContainer, IServiceManager } from '../../client/ioc/types'; import { IDiscoveryAPI } from '../../client/pythonEnvironments/base/locator'; import { initializeExternalDependencies } from '../../client/pythonEnvironments/common/externalDependencies'; -import { registerLegacyDiscoveryForIOC, registerNewDiscoveryForIOC } from '../../client/pythonEnvironments/legacyIOC'; +import { registerNewDiscoveryForIOC } from '../../client/pythonEnvironments/legacyIOC'; /** * This is here to support old tests. @@ -18,5 +18,4 @@ export async function registerForIOC( initializeExternalDependencies(serviceContainer); // The old tests do not need real instances, directly pass in mocks. registerNewDiscoveryForIOC(serviceManager, instance(mock())); - await registerLegacyDiscoveryForIOC(serviceManager); } diff --git a/src/test/pythonEnvironments/legacyIOC.unit.test.ts b/src/test/pythonEnvironments/legacyIOC.unit.test.ts deleted file mode 100644 index 640e16295037..000000000000 --- a/src/test/pythonEnvironments/legacyIOC.unit.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { instance, mock, verify, when } from 'ts-mockito'; -import { IExtensionSingleActivationService } from '../../client/activation/types'; -import { DiscoveryVariants } from '../../client/common/experiments/groups'; -import { ExperimentService } from '../../client/common/experiments/service'; -import { IExperimentService } from '../../client/common/types'; -import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GLOBAL_VIRTUAL_ENV_SERVICE, - ICondaService, - IInterpreterLocatorHelper, - IInterpreterLocatorProgressService, - IInterpreterLocatorService, - IInterpreterWatcher, - IInterpreterWatcherBuilder, - IKnownSearchPathsForInterpreters, - INTERPRETER_LOCATOR_SERVICE, - IVirtualEnvironmentsSearchPathProvider, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../client/interpreter/contracts'; -import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../../client/interpreter/locators/types'; -import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; -import { ServiceContainer } from '../../client/ioc/container'; -import { ServiceManager } from '../../client/ioc/serviceManager'; -import { initializeExternalDependencies } from '../../client/pythonEnvironments/common/externalDependencies'; -import { PythonInterpreterLocatorService } from '../../client/pythonEnvironments/discovery/locators'; -import { InterpreterLocatorHelper } from '../../client/pythonEnvironments/discovery/locators/helpers'; -import { InterpreterLocatorProgressService } from '../../client/pythonEnvironments/discovery/locators/progressService'; -import { CondaEnvFileService } from '../../client/pythonEnvironments/discovery/locators/services/condaEnvFileService'; -import { CondaEnvService } from '../../client/pythonEnvironments/discovery/locators/services/condaEnvService'; -import { CondaService } from '../../client/pythonEnvironments/discovery/locators/services/condaService'; -import { - CurrentPathService, - PythonInPathCommandProvider, -} from '../../client/pythonEnvironments/discovery/locators/services/currentPathService'; -import { - GlobalVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvService, -} from '../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService'; -import { InterpreterWatcherBuilder } from '../../client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder'; -import { - KnownPathsService, - KnownSearchPathsForInterpreters, -} from '../../client/pythonEnvironments/discovery/locators/services/KnownPathsService'; -import { PipEnvService } from '../../client/pythonEnvironments/discovery/locators/services/pipEnvService'; -import { PipEnvServiceHelper } from '../../client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper'; -import { WindowsRegistryService } from '../../client/pythonEnvironments/discovery/locators/services/windowsRegistryService'; -import { - WorkspaceVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvService, -} from '../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService'; -import { WorkspaceVirtualEnvWatcherService } from '../../client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService'; -import { registerLegacyDiscoveryForIOC } from '../../client/pythonEnvironments/legacyIOC'; - -suite('Interpreters - Service Registry', () => { - test('Registrations', async () => { - const serviceManager = mock(ServiceManager); - const serviceContainer = mock(ServiceContainer); - const experimentService = mock(ExperimentService); - - when(serviceContainer.get(IExperimentService)).thenReturn(instance(experimentService)); - when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(false); - when(experimentService.inExperiment(DiscoveryVariants.discoveryWithoutFileWatching)).thenResolve(false); - - initializeExternalDependencies(instance(serviceContainer)); - await registerLegacyDiscoveryForIOC(instance(serviceManager)); - - verify( - serviceManager.addSingleton(IInterpreterLocatorHelper, InterpreterLocatorHelper), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - PythonInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvFileService, - CONDA_ENV_FILE_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IVirtualEnvironmentManager, - VirtualEnvironmentManager, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvService, - CONDA_ENV_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - GlobalVirtualEnvService, - GLOBAL_VIRTUAL_ENV_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvironmentsSearchPathProvider, - 'global', - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - KnownPathsService, - KNOWN_PATH_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IKnownSearchPathsForInterpreters, - KnownSearchPathsForInterpreters, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorProgressService, - InterpreterLocatorProgressService, - ), - ).once(); - verify(serviceManager.addBinding(IInterpreterLocatorProgressService, IExtensionSingleActivationService)).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - CurrentPathService, - CURRENT_PATH_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IPythonInPathCommandProvider, - PythonInPathCommandProvider, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - WorkspaceVirtualEnvService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - PipEnvService, - PIPENV_SERVICE, - ), - ).once(); - - verify( - serviceManager.addSingleton( - IInterpreterLocatorService, - WindowsRegistryService, - WINDOWS_REGISTRY_SERVICE, - ), - ).once(); - verify(serviceManager.addSingleton(ICondaService, CondaService)).once(); - verify(serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper)).once(); - - verify( - serviceManager.add( - IInterpreterWatcher, - WorkspaceVirtualEnvWatcherService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ), - ).once(); - verify( - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvironmentsSearchPathProvider, - 'workspace', - ), - ).once(); - verify( - serviceManager.addSingleton( - IInterpreterWatcherBuilder, - InterpreterWatcherBuilder, - ), - ).once(); - }); -});