From 7b8960769b0f380bf306d3b5cc5165c65c5f3f5d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 14:22:11 -0700 Subject: [PATCH 01/24] Add resolver to be used --- .../pythonEnvironments/base/info/env.ts | 7 +- .../base/locators/composite/resolverUtils.ts | 90 +++++++++++++++++++ .../common/externalDependencies.ts | 4 + .../discovery/locators/services/conda.ts | 14 +++ 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index 840163677c33..aa91daba2146 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -269,14 +269,15 @@ export async function getMaxDerivedEnvInfo(minimal: PythonEnvInfo): Promise): (env: PythonEnvInfo) => boolean { +export function getEnvMatcher(query: string | Partial): (env: string | PythonEnvInfo) => boolean { const executable = getEnvExecutable(query); if (executable === '') { // We could throw an exception error, but skipping it is fine. return () => false; } - function matchEnv(candidate: PythonEnvInfo): boolean { - return arePathsSame(executable, candidate.executable.filename); + function matchEnv(candidate: string | PythonEnvInfo): boolean { + const candidateExecutable = typeof candidate === 'string' ? candidate : candidate.executable.filename; + return arePathsSame(executable, candidateExecutable); } return matchEnv; } diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts new file mode 100644 index 000000000000..fb601e908599 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { traceVerbose } from '../../../../common/logger'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info'; +import { buildEnvInfo, getEnvMatcher } from '../../info/env'; +import { + getEnvironmentDirFromPath, + getInterpreterPathFromDir, + getPythonVersionFromPath, +} from '../../../common/commonUtils'; +import { identifyEnvironment } from '../../../common/environmentIdentifier'; +import { getFileInfo, getWorkspaceFolders, isParentPath } from '../../../common/externalDependencies'; +import { AnacondaCompanyName, Conda } from '../../../discovery/locators/services/conda'; + +export async function resolveEnv(executablePath: string): Promise { + const kind = await identifyEnvironment(executablePath); + const resolved = + kind === PythonEnvKind.Conda + ? await resolveCondaEnv(executablePath) + : await resolveSimpleEnv(executablePath, kind); + + if (resolved) { + const folders = getWorkspaceFolders(); + const isRootedEnv = folders.some((f) => isParentPath(executablePath, f)); + if (isRootedEnv) { + // For environments inside roots, we need to set search location so they can be queried accordingly. + // Search location particularly for virtual environments is intended as the directory in which the + // environment was found in. + // For eg.the default search location for an env containing 'bin' or 'Scripts' directory is: + // + // searchLocation <--- Default search location directory + // |__ env + // |__ bin or Scripts + // |__ python <--- executable + resolved.searchLocation = Uri.file(path.dirname(resolved.location)); + } + } + return resolved; +} + +async function resolveSimpleEnv(executablePath: string, kind: PythonEnvKind): Promise { + const envInfo = buildEnvInfo({ + kind, + version: await getPythonVersionFromPath(executablePath), + executable: executablePath, + source: [PythonEnvSource.Other], + }); + const location = getEnvironmentDirFromPath(executablePath); + envInfo.location = location; + envInfo.name = path.basename(location); + + // TODO: Call a general display name provider here to build display name. + const fileData = await getFileInfo(executablePath); + envInfo.executable.ctime = fileData.ctime; + envInfo.executable.mtime = fileData.mtime; + return envInfo; +} + +async function resolveCondaEnv(env: string): Promise { + const conda = await Conda.getConda(); + if (conda === undefined) { + traceVerbose(`Couldn't locate the conda binary in resolver`); + return undefined; + } + traceVerbose(`Searching for conda environments using ${conda.command} in resolver`); + const envs = await conda.getEnvList(); + const matchEnv = getEnvMatcher(env); + for (const { name, prefix } of envs) { + const executable = await getInterpreterPathFromDir(prefix); + if (executable && matchEnv(executable)) { + const info = buildEnvInfo({ + executable, + kind: PythonEnvKind.Conda, + org: AnacondaCompanyName, + location: prefix, + source: [PythonEnvSource.Conda], + version: await getPythonVersionFromPath(executable), + fileInfo: await getFileInfo(executable), + }); + if (name) { + info.name = name; + } + return info; + } + } + return undefined; +} diff --git a/src/client/pythonEnvironments/common/externalDependencies.ts b/src/client/pythonEnvironments/common/externalDependencies.ts index 501e17061f02..e92f26a848c0 100644 --- a/src/client/pythonEnvironments/common/externalDependencies.ts +++ b/src/client/pythonEnvironments/common/externalDependencies.ts @@ -106,6 +106,10 @@ export function arePathsSame(path1: string, path2: string): boolean { return normCasePath(path1) === normCasePath(path2); } +export function getWorkspaceFolders(): string[] { + return vscode.workspace.workspaceFolders?.map((w) => w.uri.fsPath) ?? []; +} + export async function getFileInfo(filePath: string): Promise<{ ctime: number; mtime: number }> { try { const data = await fsapi.lstat(filePath); diff --git a/src/client/pythonEnvironments/discovery/locators/services/conda.ts b/src/client/pythonEnvironments/discovery/locators/services/conda.ts index cbd1bb1bba3b..91a17b2e4f77 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/conda.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/conda.ts @@ -207,6 +207,12 @@ export async function getPythonVersionFromConda(interpreterPath: string): Promis /** Wraps the "conda" utility, and exposes its functionality. */ export class Conda { + // Locating conda binary is expensive, since it potentially involves spawning or + // trying to spawn processes; so it's done lazily and asynchronously. Methods that + // need a Conda instance should use getConda() to obtain it, and should never access + // this property directly. + private static condaPromise: Promise | undefined; + /** * Creates a Conda service corresponding to the corresponding "conda" command. * @@ -215,6 +221,14 @@ export class Conda { */ constructor(readonly command: string) {} + public static async getConda(): Promise { + traceVerbose(`Searching for conda.`); + if (this.condaPromise === undefined) { + this.condaPromise = Conda.locate(); + } + return this.condaPromise; + } + /** * Locates the preferred "conda" utility on this system by considering user settings, * binaries on PATH, Python interpreters in the registry, and known install locations. From 78c378f19cb8d87950f974da3aa7700d3a52116c Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 14:27:19 -0700 Subject: [PATCH 02/24] Use it in environments resolver --- src/client/pythonEnvironments/base/locator.ts | 18 ++++++++++++++++++ .../locators/composite/environmentsResolver.ts | 8 +++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index cae4357ecfd2..2eb1ff8d4d60 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -165,6 +165,24 @@ export interface ILocator; } +export interface IResolver { + /** + * Find the given Python environment and fill in as much missing info as possible. + * + * If the locator can find the environment then the result is as + * much info about that env as the locator has. At the least this + * will include all the `PythonEnvBaseInfo` data. If a `PythonEnvInfo` + * was provided then the result will be a copy with any updates or + * extra info applied. + * + * If the locator could not find the environment then `undefined` + * is returned. + * + * @param env - the Python executable path or partial env info to find and update + */ + resolveEnv(env: string): Promise; +} + interface IEmitter { fire(e: E): void; } diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index 63fe1fdd60b7..1e81263c5e02 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -7,14 +7,15 @@ import { traceVerbose } from '../../../../common/logger'; import { IEnvironmentInfoService } from '../../../info/environmentInfoService'; import { PythonEnvInfo } from '../../info'; import { InterpreterInformation } from '../../info/interpreter'; -import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../locator'; +import { ILocator, IPythonEnvsIterator, IResolver, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; +import { resolveEnv } from './resolverUtils'; /** * Calls environment info service which runs `interpreterInfo.py` script on environments received * from the parent locator. Uses information received to populate environments further and pass it on. */ -export class PythonEnvsResolver implements ILocator { +export class PythonEnvsResolver implements ILocator, IResolver { public get onChanged(): Event { return this.parentLocator.onChanged; } @@ -25,7 +26,8 @@ export class PythonEnvsResolver implements ILocator { ) {} public async resolveEnv(env: string | PythonEnvInfo): Promise { - const environment = await this.parentLocator.resolveEnv(env); + const executablePath = typeof env === 'string' ? env : env.executable.filename; + const environment = await resolveEnv(executablePath); if (!environment) { return undefined; } From d3c46926fa305e342ef86ae59c4e3e57fa058477 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 16:02:29 -0700 Subject: [PATCH 03/24] Remove esolveEnv from ILocator interface --- src/client/pythonEnvironments/api.ts | 6 +- src/client/pythonEnvironments/base/locator.ts | 25 +-- .../pythonEnvironments/base/locators.ts | 11 - .../locators/common/resourceBasedLocator.ts | 11 - .../base/locators/common/wrappingLocator.ts | 8 +- .../base/locators/composite/cachingLocator.ts | 8 +- .../locators/composite/environmentsReducer.ts | 12 -- .../composite/environmentsResolver.ts | 10 +- .../base/locators/lowLevel/filesLocator.ts | 7 +- .../lowLevel/windowsKnownPathsLocator.ts | 23 +- .../discovery/locators/index.ts | 23 -- src/client/pythonEnvironments/index.ts | 13 +- src/client/pythonEnvironments/legacyIOC.ts | 4 +- .../base/locators.unit.test.ts | 91 -------- .../environmentsReducer.unit.test.ts | 60 +----- ...indowsKnownPathsLocator.functional.test.ts | 203 +----------------- 16 files changed, 34 insertions(+), 481 deletions(-) diff --git a/src/client/pythonEnvironments/api.ts b/src/client/pythonEnvironments/api.ts index f8b22a99a9c5..487d512b4bd3 100644 --- a/src/client/pythonEnvironments/api.ts +++ b/src/client/pythonEnvironments/api.ts @@ -4,7 +4,7 @@ import { Event } from 'vscode'; import { Disposables, IDisposable } from '../common/utils/resourceLifecycle'; import { PythonEnvInfo } from './base/info'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from './base/locator'; +import { IPythonEnvsIterator, IResolvingLocator, PythonLocatorQuery } from './base/locator'; import { GetLocatorFunc, LazyWrappingLocator } from './base/locators/common/wrappingLocator'; import { PythonEnvsChangedEvent } from './base/watcher'; @@ -13,10 +13,10 @@ import { PythonEnvsChangedEvent } from './base/watcher'; * * Note that this is composed of sub-components. */ -export class PythonEnvironments implements ILocator, IDisposable { +export class PythonEnvironments implements IResolvingLocator, IDisposable { private readonly disposables = new Disposables(); - private readonly locators: ILocator; + private readonly locators: IResolvingLocator; constructor( // These are factories for the sub-components the full component is composed of: diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 2eb1ff8d4d60..8dec41b0cb12 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -147,7 +147,9 @@ export interface ILocator): IPythonEnvsIterator; +} +interface IResolver { /** * Find the given Python environment and fill in as much missing info as possible. * @@ -165,23 +167,7 @@ export interface ILocator; } -export interface IResolver { - /** - * Find the given Python environment and fill in as much missing info as possible. - * - * If the locator can find the environment then the result is as - * much info about that env as the locator has. At the least this - * will include all the `PythonEnvBaseInfo` data. If a `PythonEnvInfo` - * was provided then the result will be a copy with any updates or - * extra info applied. - * - * If the locator could not find the environment then `undefined` - * is returned. - * - * @param env - the Python executable path or partial env info to find and update - */ - resolveEnv(env: string): Promise; -} +export interface IResolvingLocator extends IResolver, ILocator {} interface IEmitter { fire(e: E): void; @@ -211,11 +197,6 @@ abstract class LocatorBase): IPythonEnvsIterator; - - // eslint-disable-next-line class-methods-use-this - public async resolveEnv(_env: string | PythonEnvInfo): Promise { - return undefined; - } } /** diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 9bf85f4778fd..842237d59fd4 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -3,7 +3,6 @@ import { chain } from '../../common/utils/async'; import { Disposables } from '../../common/utils/resourceLifecycle'; -import { PythonEnvInfo } from './info'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery } from './locator'; import { PythonEnvsWatchers } from './watchers'; @@ -59,14 +58,4 @@ export class Locators extends PythonEnvsWatchers implements ILocator { const iterators = this.locators.map((loc) => loc.iterEnvs(query)); return combineIterators(iterators); } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - for (const locator of this.locators) { - const resolved = await locator.resolveEnv(env); - if (resolved !== undefined) { - return resolved; - } - } - return undefined; - } } diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index 77e646288348..74735967c586 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -3,7 +3,6 @@ import { createDeferred, Deferred } from '../../../../common/utils/async'; import { Disposables, IDisposable } from '../../../../common/utils/resourceLifecycle'; -import { PythonEnvInfo } from '../../info'; import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; /** @@ -38,21 +37,11 @@ export abstract class LazyResourceBasedLocator extends Locator implements IDispo this.ensureWatchersReady().ignoreErrors(); } - public async resolveEnv(env: string | PythonEnvInfo): Promise { - await this.ensureResourcesReady(); - return this.doResolveEnv(env); - } - /** * The subclass implementation of iterEnvs(). */ protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; - /** - * The subclass implementation of resolveEnv(). - */ - protected abstract doResolveEnv(_env: string | PythonEnvInfo): Promise; - /** * This is where subclasses get their resources ready. * diff --git a/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts b/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts index 76431fc2869a..3a844d81ada6 100644 --- a/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts @@ -4,11 +4,11 @@ import { Event } from 'vscode'; import { IDisposable } from '../../../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from '../../info'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator'; +import { IPythonEnvsIterator, IResolvingLocator, PythonLocatorQuery } from '../../locator'; import { PythonEnvsChangedEvent, PythonEnvsWatcher } from '../../watcher'; import { LazyResourceBasedLocator } from './resourceBasedLocator'; -export type GetLocatorFunc = () => Promise>; +export type GetLocatorFunc = () => Promise>; /** * A locator that wraps another. @@ -20,7 +20,7 @@ export class LazyWrappingLocator extends LazyResourceBasedLocator { private readonly watcher = new PythonEnvsWatcher(); - private wrapped?: ILocator; + private wrapped?: IResolvingLocator; constructor(private readonly getLocator: GetLocatorFunc) { super(); @@ -31,7 +31,7 @@ export class LazyWrappingLocator extends LazyResourceBasedLocator { yield* this.wrapped!.iterEnvs(query); } - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { + public async resolveEnv(env: string | PythonEnvInfo): Promise { return this.wrapped!.resolveEnv(env); } diff --git a/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts b/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts index 55b61bab8bbb..d18131696c16 100644 --- a/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts @@ -8,7 +8,7 @@ import { logWarning } from '../../../../logging'; import { IEnvsCache } from '../../envsCache'; import { PythonEnvInfo } from '../../info'; import { getMinimalPartialInfo } from '../../info/env'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator'; +import { IPythonEnvsIterator, IResolvingLocator, PythonLocatorQuery } from '../../locator'; import { getEnvs, getQueryFilter } from '../../locatorUtils'; import { PythonEnvsChangedEvent, PythonEnvsWatcher } from '../../watcher'; import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; @@ -17,7 +17,7 @@ import { pickBestEnv } from './reducingLocator'; /** * A locator that stores the known environments in the given cache. */ -export class CachingLocator extends LazyResourceBasedLocator { +export class CachingLocator extends LazyResourceBasedLocator implements IResolvingLocator { public readonly onChanged: Event; private readonly watcher = new PythonEnvsWatcher(); @@ -26,7 +26,7 @@ export class CachingLocator extends LazyResourceBasedLocator { private refreshCache?: () => Promise; - constructor(private readonly cache: IEnvsCache, private readonly locator: ILocator) { + constructor(private readonly cache: IEnvsCache, private readonly locator: IResolvingLocator) { super(); this.onChanged = this.watcher.onChanged; } @@ -39,7 +39,7 @@ export class CachingLocator extends LazyResourceBasedLocator { yield* this.iterFromCache(query); } - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { + public async resolveEnv(env: string | PythonEnvInfo): Promise { let matchingEnvs = this.filterMatchingEnvsFromCache(env); if (matchingEnvs.length > 0) { return pickBestEnv(matchingEnvs); diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts index 7f1b423c5259..e6c8c261ed50 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -7,7 +7,6 @@ import { traceVerbose } from '../../../../common/logger'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { areSameEnv, mergeEnvironments } from '../../info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../locator'; -import { getEnvs } from '../../locatorUtils'; import { PythonEnvsChangedEvent } from '../../watcher'; /** @@ -20,17 +19,6 @@ export class PythonEnvsReducer implements ILocator { constructor(private readonly parentLocator: ILocator) {} - public async resolveEnv(env: string | PythonEnvInfo): Promise { - const environments = await getEnvs(this.iterEnvs()); - let environment: string | PythonEnvInfo | undefined = environments.find((e) => areSameEnv(e, env)); - if (!environment) { - // It isn't one we've reduced, but fall back - // to the wrapped locator anyway. - environment = env; - } - return this.parentLocator.resolveEnv(environment); - } - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const didUpdate = new EventEmitter(); const incomingIterator = this.parentLocator.iterEnvs(query); diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index 1e81263c5e02..d2acacb5c1f9 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -7,7 +7,13 @@ import { traceVerbose } from '../../../../common/logger'; import { IEnvironmentInfoService } from '../../../info/environmentInfoService'; import { PythonEnvInfo } from '../../info'; import { InterpreterInformation } from '../../info/interpreter'; -import { ILocator, IPythonEnvsIterator, IResolver, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../locator'; +import { + ILocator, + IPythonEnvsIterator, + IResolvingLocator, + PythonEnvUpdatedEvent, + PythonLocatorQuery, +} from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; import { resolveEnv } from './resolverUtils'; @@ -15,7 +21,7 @@ import { resolveEnv } from './resolverUtils'; * Calls environment info service which runs `interpreterInfo.py` script on environments received * from the parent locator. Uses information received to populate environments further and pass it on. */ -export class PythonEnvsResolver implements ILocator, IResolver { +export class PythonEnvsResolver implements IResolvingLocator { public get onChanged(): Event { return this.parentLocator.onChanged; } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts index 301625ea1ac0..152ec51e519f 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts @@ -12,7 +12,7 @@ import { resolvePath } from '../../../common/externalDependencies'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { getFastEnvInfo } from '../../info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery } from '../../locator'; -import { iterAndUpdateEnvs, resolveEnvFromIterator } from '../../locatorUtils'; +import { iterAndUpdateEnvs } from '../../locatorUtils'; import { PythonEnvsChangedEvent, PythonEnvsWatcher } from '../../watcher'; type Executable = string | DirEntry; @@ -49,11 +49,6 @@ class FoundFilesLocator implements ILocator { iterator.onUpdated = emitter.event; return iterator; } - - public async resolveEnv(env: string | Partial): Promise { - const iterator = this.iterEnvs(); - return resolveEnvFromIterator(env, iterator); - } } /** diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts index fa36db59eda0..5437495078ab 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts @@ -11,7 +11,7 @@ import { Disposables, IDisposable } from '../../../../common/utils/resourceLifec import { iterPythonExecutablesInDir, looksLikeBasicGlobalPython } from '../../../common/commonUtils'; import { isPyenvShimDir } from '../../../discovery/locators/services/pyenvLocator'; import { isWindowsStoreDir } from '../../../discovery/locators/services/windowsStoreLocator'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info'; +import { PythonEnvKind, PythonEnvSource } from '../../info'; import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator'; import { Locators } from '../../locators'; import { getEnvs } from '../../locatorUtils'; @@ -63,10 +63,6 @@ export class WindowsPathEnvVarLocator implements ILocator, IDisposable { // locators). return this.locators.iterEnvs(query); } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - return this.locators.resolveEnv(env); - } } async function* getExecutables(dirname: string): AsyncIterableIterator { @@ -100,25 +96,8 @@ function getDirFilesLocator( yield env; } } - async function resolveEnv(env: string | PythonEnvInfo): Promise { - const executable = typeof env === 'string' ? env : env.executable?.filename || ''; - - if (!(await looksLikeBasicGlobalPython(executable))) { - return undefined; - } - const resolved = await locator.resolveEnv(env); - if (resolved) { - const source = - typeof env === 'string' - ? [PythonEnvSource.PathEnvVar] - : uniq([...env.source, PythonEnvSource.PathEnvVar]); - resolved.source = source; - } - return resolved; - } return { iterEnvs, - resolveEnv, dispose, onChanged: locator.onChanged, }; diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts index 2bedccea203a..78f8a10c6646 100644 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ b/src/client/pythonEnvironments/discovery/locators/index.ts @@ -24,7 +24,6 @@ import { WORKSPACE_VIRTUAL_ENV_SERVICE, } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvInfo } from '../../base/info'; import { ILocator, IPythonEnvsIterator, NOOP_ITERATOR, PythonLocatorQuery } from '../../base/locator'; import { combineIterators, Locators } from '../../base/locators'; import { LazyResourceBasedLocator } from '../../base/locators/common/resourceBasedLocator'; @@ -101,28 +100,6 @@ export class WorkspaceLocators extends LazyResourceBasedLocator { return combineIterators(iterators); } - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - if (typeof env !== 'string' && env.searchLocation) { - const found = this.locators[env.searchLocation.toString()]; - if (found !== undefined) { - const [rootLocator] = found; - return rootLocator.resolveEnv(env); - } - } - // Fall back to checking all the roots. - // The eslint disable below should be removed after we have a - // better solution for these. We need asyncFind for this. - for (const key of Object.keys(this.locators)) { - const [locator] = this.locators[key]; - // eslint-disable-next-line no-await-in-loop - const resolved = await locator.resolveEnv(env); - if (resolved !== undefined) { - return resolved; - } - } - return undefined; - } - protected async initResources(): Promise { const disposable = this.watchRoots({ initRoot: (root: Uri) => this.addRoot(root), diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 4a7b58487318..54590ee17cef 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -9,7 +9,7 @@ import { ActivationResult, ExtensionState } from '../components'; import { PythonEnvironments } from './api'; import { getPersistentCache } from './base/envsCache'; import { PythonEnvInfo } from './base/info'; -import { ILocator } from './base/locator'; +import { ILocator, IResolvingLocator } from './base/locator'; import { CachingLocator } from './base/locators/composite/cachingLocator'; import { PythonEnvsReducer } from './base/locators/composite/environmentsReducer'; import { PythonEnvsResolver } from './base/locators/composite/environmentsResolver'; @@ -81,7 +81,7 @@ export async function activate(api: PythonEnvironments): Promise { +): Promise { // Create the low-level locators. let locators: ILocator = new ExtensionLocators( // Here we pull the locators together. @@ -94,7 +94,7 @@ async function createLocators( // Build the stack of composite locators. locators = new PythonEnvsReducer(locators); - locators = new PythonEnvsResolver( + const resolvingLocator = new PythonEnvsResolver( locators, // These are shared. envInfoService, @@ -102,12 +102,11 @@ async function createLocators( const caching = await createCachingLocator( ext, // This is shared. - locators, + resolvingLocator, ); ext.disposables.push(caching); - locators = caching; - return locators; + return caching; } function createNonWorkspaceLocators(ext: ExtensionState): ILocator[] { @@ -166,7 +165,7 @@ function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { return locators; } -async function createCachingLocator(ext: ExtensionState, locators: ILocator): Promise { +async function createCachingLocator(ext: ExtensionState, locators: IResolvingLocator): Promise { const storage = getGlobalStorage(ext.context, 'PYTHON_ENV_INFO_CACHE'); const cache = await getPersistentCache( { diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index fc939cf5c617..b2463d306c2f 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -36,7 +36,7 @@ import { IVirtualEnvironmentManager } from '../interpreter/virtualEnvs/types'; import { IServiceManager } from '../ioc/types'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './base/info'; import { buildEnvInfo } from './base/info/env'; -import { ILocator, PythonLocatorQuery } from './base/locator'; +import { IResolvingLocator, PythonLocatorQuery } from './base/locator'; import { isMacDefaultPythonPath } from './base/locators/lowLevel/macDefaultLocator'; import { getEnvs } from './base/locatorUtils'; import { inExperiment, isParentPath } from './common/externalDependencies'; @@ -133,7 +133,7 @@ export async function isComponentEnabled(): Promise { return results.includes(true); } -interface IPythonEnvironments extends ILocator {} +interface IPythonEnvironments extends IResolvingLocator {} @injectable() class ComponentAdapter implements IComponentAdapter { diff --git a/src/test/pythonEnvironments/base/locators.unit.test.ts b/src/test/pythonEnvironments/base/locators.unit.test.ts index e71bcf9f04ef..f3ce16e6491e 100644 --- a/src/test/pythonEnvironments/base/locators.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators.unit.test.ts @@ -186,95 +186,4 @@ suite('Python envs locators - Locators', () => { assert.deepEqual(envs, expected); }); }); - - suite('resolveEnv()', () => { - test('one wrapped', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const calls: number[] = []; - const sub1 = new SimpleLocator([env1], { - resolve: async (e) => { - calls.push(1); - return e; - }, - }); - const locators = new Locators([sub1]); - - const resolved = await locators.resolveEnv(env1); - - assert.deepEqual(resolved, expected); - assert.deepEqual(calls, [1]); - }); - - test('first one resolves', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const calls: number[] = []; - const sub1 = new SimpleLocator([env1], { - resolve: async (e) => { - calls.push(1); - return e; - }, - }); - const sub2 = new SimpleLocator([env1], { - resolve: async (e) => { - calls.push(2); - return e; - }, - }); - const locators = new Locators([sub1, sub2]); - - const resolved = await locators.resolveEnv(env1); - - assert.deepEqual(resolved, expected); - assert.deepEqual(calls, [1]); - }); - - test('second one resolves', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const calls: number[] = []; - const sub1 = new SimpleLocator([env1], { - resolve: async () => { - calls.push(1); - return undefined; - }, - }); - const sub2 = new SimpleLocator([env1], { - resolve: async (e) => { - calls.push(2); - return e; - }, - }); - const locators = new Locators([sub1, sub2]); - - const resolved = await locators.resolveEnv(env1); - - assert.deepEqual(resolved, expected); - assert.deepEqual(calls, [1, 2]); - }); - - test('none resolve', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const calls: number[] = []; - const sub1 = new SimpleLocator([env1], { - resolve: async () => { - calls.push(1); - return undefined; - }, - }); - const sub2 = new SimpleLocator([env1], { - resolve: async () => { - calls.push(2); - return undefined; - }, - }); - const locators = new Locators([sub1, sub2]); - - const resolved = await locators.resolveEnv(env1); - - assert.equal(resolved, undefined); - assert.deepEqual(calls, [1, 2]); - }); - }); }); diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts index 3865572ac780..a41cf070dba2 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { isEqual } from 'lodash'; import * as path from 'path'; import { EventEmitter } from 'vscode'; -import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import { PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator'; import { PythonEnvsReducer } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer'; import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; @@ -219,62 +219,4 @@ suite('Python envs locator - Environments Reducer', () => { assert.deepEqual(events, expected); }); - - suite('resolveEnv()', () => { - test('Iterates environments from the reducer to get resolved environment, then calls into locator manager to resolve environment further and return it', async () => { - const env1 = createNamedEnv('env', '3.8', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); - const env2 = createNamedEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env3 = createNamedEnv('env', '3.8.1b1', PythonEnvKind.System, path.join('path', 'to', 'exec')); - const env4 = createNamedEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env5 = createNamedEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env6 = createNamedEnv('env', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same - - const env136 = createNamedEnv('env', '3.8.1b1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); - const expected = createNamedEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); - const parentLocator = new SimpleLocator(environmentsToBeIterated, { - resolve: async (e: PythonEnvInfo) => { - if (isEqual(e, env136)) { - return expected; - } - throw new Error('Incorrect environment sent to the resolve'); - }, - }); - const reducer = new PythonEnvsReducer(parentLocator); - - // Trying to resolve the environment corresponding to env1 env3 env6 - const resolved = await reducer.resolveEnv(path.join('path', 'to', 'exec')); - - assert.deepEqual(resolved, expected); - }); - - test("If the reducer isn't able to resolve environment, fall back to the wrapped locator", async () => { - const env1 = createNamedEnv('env', '3.8', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); - const env2 = createNamedEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env3 = createNamedEnv('env', '3.8.1b1', PythonEnvKind.System, path.join('path', 'to', 'exec')); - const env4 = createNamedEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env5 = createNamedEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env6 = createNamedEnv('env', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same - - const filename1 = path.join('resolved', 'path', 'to', 'execNeverSeenBefore'); - const filename2 = path.join('resolved', 'path', 'to', 'execAlsoNeverSeenBefore'); - const expected = createNamedEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, filename1); - const parentLocator = new SimpleLocator(environmentsToBeIterated, { - resolve: async (e: PythonEnvInfo) => { - if (e.executable.filename === expected.executable.filename) { - return expected; - } - return undefined; - }, - }); - const reducer = new PythonEnvsReducer(parentLocator); - - const resolved1 = await reducer.resolveEnv(filename1); - const resolved2 = await reducer.resolveEnv(filename2); - - assert.deepEqual(resolved1, expected); - assert.equal(resolved2, undefined); - }); - }); }); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.functional.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.functional.test.ts index 10589dad9fea..347fa93d5d75 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.functional.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.functional.test.ts @@ -4,13 +4,7 @@ import { assert } from 'chai'; import * as path from 'path'; import { Architecture, getOSType, OSType } from '../../../../../client/common/utils/platform'; -import { - PythonEnvInfo, - PythonEnvKind, - PythonEnvSource, - PythonExecutableInfo, - UNKNOWN_PYTHON_VERSION, -} from '../../../../../client/pythonEnvironments/base/info'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../../../client/pythonEnvironments/base/info'; import { PythonLocatorQuery } from '../../../../../client/pythonEnvironments/base/locator'; import { WindowsPathEnvVarLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator'; import { ensureFSTree } from '../../../../utils/fs'; @@ -18,13 +12,6 @@ import { createNamedEnv, getEnvs, sortedEnvs } from '../../common'; const IS_WINDOWS = getOSType() === OSType.Windows; -const EMPTY_EXECUTABLE: PythonExecutableInfo = { - filename: '', - ctime: -1, - mtime: -1, - sysPrefix: '', -}; - function getEnv( // These will all be provided. name: string, @@ -182,192 +169,4 @@ suite('Python envs locator - WindowsPathEnvVarLocator', async () => { assert.deepEqual(sortedEnvs(envs), sortedEnvs(expected)); }); }); - - suite('resolveEnv()', () => { - test('found using filename', async () => { - const filename = path.join(ROOT1, 'python.exe'); - const expected = getEnv('', '', filename); - // We will expect the following once we switch - // to a better filter than isStandardPythonBinary(). - // - // const filename = path.join(ROOT1, 'python3.8.exe'); - // const expected = getEnv('', '3.8', filename); - const locator = getActiveLocator(ROOT2, ROOT6, ROOT1); - - const resolved = await locator.resolveEnv(filename); - - assert.deepEqual(resolved, expected); - }); - - test('found using env info', async () => { - const filename = path.join(ROOT1, 'python.exe'); - const env = { - kind: PythonEnvKind.Unknown, - name: '', - location: '', - executable: { ...EMPTY_EXECUTABLE, filename }, - source: [], - version: UNKNOWN_PYTHON_VERSION, - arch: Architecture.Unknown, - distro: { org: '' }, - }; - const expected = getEnv('', '', filename); - // We will expect the following once we switch - // to a better filter than isStandardPythonBinary(). - // - // const filename = path.join(ROOT1, 'python3.8.exe'); - // const env = { - // executable: { ...EMPTY_EXECUTABLE, filename }, - // }; - // const expected = getEnv('', '3.8', filename); - const locator = getActiveLocator(ROOT2, ROOT6, ROOT1); - - const resolved = await locator.resolveEnv(env as PythonEnvInfo); - - assert.deepEqual(resolved, expected); - }); - - [ - // We run through these as a sanity check. - path.join(ROOT2, 'python2.exe'), - path.join(ROOT1, 'python3.8.exe'), - path.join(ROOT1, 'python3.8.1rc1.10213.exe'), - path.join(ROOT1, 'my-python.exe'), - path.join(ROOT4, 'python2.exe'), - path.join(ROOT5, 'subdir', 'python2.exe'), - path.join(ROOT6, 'spam.exe'), - path.join(ROOT6, 'py.exe'), - ].forEach((executable) => { - test(`no executables found (${executable})`, async () => { - const locator = getActiveLocator(ROOT3, ROOT4, DOES_NOT_EXIST, ROOT5); - - const resolved = await locator.resolveEnv(executable); - - assert.equal(resolved, undefined); - }); - }); - - [ - path.join(ROOT2, 'python2.exe'), - path.join(ROOT1, 'python3.8.exe'), - path.join(ROOT5, 'subdir', 'python2.exe'), - ].forEach((executable) => { - test(`wrong search path entries (${executable})`, async () => { - const locator = getActiveLocator(ROOT6, ROOT5, DOES_NOT_EXIST); - - const resolved = await locator.resolveEnv(executable); - - assert.equal(resolved, undefined); - }); - }); - - [ - path.join(ROOT1, 'python3.8.1rc1.10213.exe'), // does not match regex - path.join(ROOT1, 'my-python.exe'), // does not match regex - path.join(ROOT6, 'spam.exe'), // does not match regex - path.join(ROOT6, 'py.exe'), // does not match regex - ].forEach((executable) => { - test(`does not match regex (${executable})`, async () => { - const locator = getActiveLocator(ROOT6, ROOT1, DOES_NOT_EXIST); - - const resolved = await locator.resolveEnv(executable); - - assert.equal(resolved, undefined); - }); - }); - - [ - path.join(ROOT4, 'python2.exe'), // not executable - ].forEach((executable) => { - test(`not executable (${executable})`, async () => { - const locator = getActiveLocator(ROOT4, DOES_NOT_EXIST); - - const resolved = await locator.resolveEnv(executable); - - assert.equal(resolved, undefined); - }); - }); - - [ - '', - { name: 'env1' }, // matches an env but resolveEnv() doesn't care - {}, - ].forEach((env) => { - test(`missing executable (${env})`, async () => { - const locator = getActiveLocator(ROOT2, ROOT6, ROOT1); - - const resolved = await locator.resolveEnv(env as string | PythonEnvInfo); - - assert.equal(resolved, undefined); - }); - }); - - test('multiple calls', async () => { - const expected: (PythonEnvInfo | undefined)[] = [ - undefined, - undefined, - undefined, - getEnv('', '', path.join(ROOT1, 'python.exe')), - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - - // We will expect the following once we switch - // to a better filter than isStandardPythonBinary(). - - // getEnv('', '2.7', path.join(ROOT2, 'python2.exe')), - // undefined, - // undefined, - // getEnv('', '', path.join(ROOT1, 'python.exe')), - // getEnv('', '2.7', path.join(ROOT1, 'python2.7.exe')), - // getEnv('', '3.8', path.join(ROOT1, 'python3.8.exe')), - // undefined, - // undefined, - // undefined, - // undefined, - // getEnv('', '3.8', path.join(ROOT1, 'python3.8.exe')), - // undefined, - // undefined, - ]; - const executables = [ - path.join(ROOT2, 'python2.exe'), - path.join(ROOT1, 'python3.8.1rc1.10213.exe'), // does not match regex - path.join(ROOT1, 'my-python.exe'), // does not match regex - path.join(ROOT1, 'python.exe'), - path.join(ROOT1, 'python2.7.exe'), - path.join(ROOT1, 'python3.8.exe'), - path.join(ROOT4, 'python.exe'), // not executable - path.join(ROOT5, 'subdir', 'python.exe'), // non on $PATH - path.join(ROOT6, 'spam.exe'), // does not match regex - path.join(ROOT6, 'py.exe'), // does not match regex - { - executable: { - ...EMPTY_EXECUTABLE, - filename: path.join(ROOT1, 'python3.8.exe'), - }, - }, - { name: 'env1' }, // matches an env but resolveEnv() doesn't care - {}, - ]; - const locator = getActiveLocator(ROOT2, ROOT6, ROOT1); - - const envs = await Promise.all( - // Each executable is resolved. - executables.map((exe) => locator.resolveEnv(exe as string | PythonEnvInfo)), - ); - - assert.deepEqual(envs, expected); - }); - }); - - // Once the locator has an FS watcher, we will need to add - // a test to verify that FS or env var changes cause the - // locator to refresh and emit an event. Until then there - // really isn't much to test with `locator.onChanged`. }); From 6d2fd8b4f2916269f9252b5c5a154fc880fcf9bc Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 16:39:28 -0700 Subject: [PATCH 04/24] Move resolveEnv for simple envs into resolver --- .../services/customVirtualEnvLocator.ts | 13 ------------- .../services/globalVirtualEnvronmentLocator.ts | 17 ----------------- .../locators/services/poetryLocator.ts | 12 ------------ .../locators/services/posixKnownPathsLocator.ts | 5 ----- 4 files changed, 47 deletions(-) diff --git a/src/client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator.ts index 5206a86f50ca..71fcf78d3dda 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator.ts @@ -159,17 +159,4 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { return iterator(); } - - // eslint-disable-next-line class-methods-use-this - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - if (await pathExists(executablePath)) { - // We should extract the kind here to avoid doing is*Environment() - // check multiple times. Those checks are file system heavy and - // we can use the kind to determine this anyway. - const kind = await getVirtualEnvKind(executablePath); - return buildSimpleVirtualEnvInfo(executablePath, kind); - } - return undefined; - } } diff --git a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts index 23c94eab13bd..3358cbcd82f4 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts @@ -165,21 +165,4 @@ export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { return iterator(); } - - // eslint-disable-next-line class-methods-use-this - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - if (await pathExists(executablePath)) { - // We should extract the kind here to avoid doing is*Environment() - // check multiple times. Those checks are file system heavy and - // we can use the kind to determine this anyway. - const kind = await getVirtualEnvKind(executablePath); - if (kind === PythonEnvKind.Unknown) { - // We don't know the environment type so skip this one. - return undefined; - } - return buildSimpleVirtualEnvInfo(executablePath, kind); - } - return undefined; - } } diff --git a/src/client/pythonEnvironments/discovery/locators/services/poetryLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/poetryLocator.ts index ba931072cb76..6f92f950bb45 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/poetryLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/poetryLocator.ts @@ -3,7 +3,6 @@ 'use strict'; -import { uniq } from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { traceError, traceVerbose } from '../../../../common/logger'; @@ -139,15 +138,4 @@ export class PoetryLocator extends FSWatchingLocator { return iterator(this.root); } - - // eslint-disable-next-line class-methods-use-this - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - const source = typeof env === 'string' ? [PythonEnvSource.Other] : uniq([PythonEnvSource.Other, ...env.source]); - if (await isPoetryEnvironment(executablePath)) { - const isLocal = isParentPath(executablePath, this.root); - return buildVirtualEnvInfo(executablePath, PythonEnvKind.Poetry, source, isLocal); - } - return undefined; - } } diff --git a/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts index 2cb5b35bc3ec..bfe091cefa57 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts @@ -35,11 +35,6 @@ export class PosixKnownPathsLocator extends Locator { return iterator(); } - public resolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - return this.buildPathEnvInfo(executablePath); - } - private async buildPathEnvInfo(bin: string): Promise { let version: PythonVersion; try { From 80288decbc93bb5b9ca6c12999938223ca5273b7 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 16:55:15 -0700 Subject: [PATCH 05/24] Move resolveEnv from pyenv locator --- .../base/locators/composite/resolverUtils.ts | 56 +++++++++++++++++-- .../locators/services/pyenvLocator.ts | 36 +----------- .../composite/resolverUtils.unit.test.ts | 41 ++++++++++++++ .../locators/pyenvLocator.functional.test.ts | 39 +------------ 4 files changed, 93 insertions(+), 79 deletions(-) create mode 100644 src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index fb601e908599..9833f1e9dc0a 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -14,14 +14,11 @@ import { import { identifyEnvironment } from '../../../common/environmentIdentifier'; import { getFileInfo, getWorkspaceFolders, isParentPath } from '../../../common/externalDependencies'; import { AnacondaCompanyName, Conda } from '../../../discovery/locators/services/conda'; +import { parsePyenvVersion } from '../../../discovery/locators/services/pyenvLocator'; +import { Architecture } from '../../../../common/utils/platform'; export async function resolveEnv(executablePath: string): Promise { - const kind = await identifyEnvironment(executablePath); - const resolved = - kind === PythonEnvKind.Conda - ? await resolveCondaEnv(executablePath) - : await resolveSimpleEnv(executablePath, kind); - + const resolved = await doResolveEnv(executablePath); if (resolved) { const folders = getWorkspaceFolders(); const isRootedEnv = folders.some((f) => isParentPath(executablePath, f)); @@ -41,6 +38,20 @@ export async function resolveEnv(executablePath: string): Promise { + const kind = await identifyEnvironment(executablePath); + switch (kind) { + case PythonEnvKind.Conda: + return resolveCondaEnv(executablePath); + case PythonEnvKind.Pyenv: + return _resolvePyenvEnv(executablePath); + case PythonEnvKind.WindowsStore: + return resolveWindowsStoreEnv(executablePath); + default: + return resolveSimpleEnv(executablePath, kind); + } +} + async function resolveSimpleEnv(executablePath: string, kind: PythonEnvKind): Promise { const envInfo = buildEnvInfo({ kind, @@ -88,3 +99,36 @@ async function resolveCondaEnv(env: string): Promise } return undefined; } + +export async function _resolvePyenvEnv(executablePath: string): Promise { + const location = getEnvironmentDirFromPath(executablePath); + const name = path.basename(location); + + const versionStrings = await parsePyenvVersion(name); + + const envInfo = buildEnvInfo({ + kind: PythonEnvKind.Pyenv, + executable: executablePath, + source: [PythonEnvSource.Pyenv], + location, + display: `${name}:pyenv`, + version: await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer), + org: versionStrings && versionStrings.distro ? versionStrings.distro : '', + fileInfo: await getFileInfo(executablePath), + }); + + envInfo.name = name; + return envInfo; +} + +async function resolveWindowsStoreEnv(executablePath: string): Promise { + return buildEnvInfo({ + kind: PythonEnvKind.WindowsStore, + executable: executablePath, + version: await getPythonVersionFromPath(executablePath), + org: 'Microsoft', + arch: Architecture.x64, + fileInfo: await getFileInfo(executablePath), + source: [PythonEnvSource.PathEnvVar], + }); +} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts index 3c72ad9a0f49..9708ea86058d 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { uniq } from 'lodash'; import * as path from 'path'; import { traceError } from '../../../../common/logger'; import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../../common/utils/platform'; @@ -9,11 +8,7 @@ import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/inf import { buildEnvInfo } from '../../../base/info/env'; import { IPythonEnvsIterator } from '../../../base/locator'; import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator'; -import { - getEnvironmentDirFromPath, - getInterpreterPathFromDir, - getPythonVersionFromPath, -} from '../../../common/commonUtils'; +import { getInterpreterPathFromDir, getPythonVersionFromPath } from '../../../common/commonUtils'; import { arePathsSame, getFileInfo, getSubDirs, pathExists } from '../../../common/externalDependencies'; function getPyenvDir(): string { @@ -331,33 +326,4 @@ export class PyenvLocator extends FSWatchingLocator { public doIterEnvs(): IPythonEnvsIterator { return getPyenvEnvironments(); } - - // eslint-disable-next-line class-methods-use-this - public async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - const source = - typeof env === 'string' ? [PythonEnvSource.Pyenv] : uniq([PythonEnvSource.Pyenv].concat(env.source)); - - if (await isPyenvEnvironment(executablePath)) { - const location = getEnvironmentDirFromPath(executablePath); - const name = path.basename(location); - - const versionStrings = await parsePyenvVersion(name); - - const envInfo = buildEnvInfo({ - kind: PythonEnvKind.Pyenv, - executable: executablePath, - source, - location, - display: `${name}:pyenv`, - version: await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer), - org: versionStrings && versionStrings.distro ? versionStrings.distro : '', - fileInfo: await getFileInfo(executablePath), - }); - - envInfo.name = name; - return envInfo; - } - return undefined; - } } diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts new file mode 100644 index 000000000000..1aa68e3154eb --- /dev/null +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../../../client/pythonEnvironments/base/info'; +import { buildEnvInfo } from '../../../../../client/pythonEnvironments/base/info/env'; +import { _resolvePyenvEnv } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; +import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; +import { assertEnvEqual } from '../../../discovery/locators/envTestUtils'; + +suite('Resolver Utils', () => { + const testPyenvRoot = path.join(TEST_LAYOUT_ROOT, 'pyenvhome', '.pyenv'); + const testPyenvVersionsDir = path.join(testPyenvRoot, 'versions'); + + function getExpectedPyenvInfo(name: string): PythonEnvInfo | undefined { + if (name === '3.9.0') { + const envInfo = buildEnvInfo({ + kind: PythonEnvKind.Pyenv, + executable: path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'), + version: { + major: 3, + minor: 9, + micro: 0, + }, + source: [PythonEnvSource.Pyenv], + }); + envInfo.display = '3.9.0:pyenv'; + envInfo.location = path.join(testPyenvVersionsDir, '3.9.0'); + envInfo.name = '3.9.0'; + return envInfo; + } + return undefined; + } + test('resolvePyenvEnv', async () => { + const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); + const expected = getExpectedPyenvInfo('3.9.0'); + + const actual = await _resolvePyenvEnv(pythonPath); + assertEnvEqual(actual, expected); + }); +}); diff --git a/src/test/pythonEnvironments/discovery/locators/pyenvLocator.functional.test.ts b/src/test/pythonEnvironments/discovery/locators/pyenvLocator.functional.test.ts index d2ea3c1e00ba..2b611aec43cc 100644 --- a/src/test/pythonEnvironments/discovery/locators/pyenvLocator.functional.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/pyenvLocator.functional.test.ts @@ -10,7 +10,7 @@ import { buildEnvInfo } from '../../../../client/pythonEnvironments/base/info/en import { getEnvs } from '../../../../client/pythonEnvironments/base/locatorUtils'; import { PyenvLocator } from '../../../../client/pythonEnvironments/discovery/locators/services/pyenvLocator'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; suite('Pyenv Locator Tests', () => { let getEnvVariableStub: sinon.SinonStub; @@ -136,41 +136,4 @@ suite('Pyenv Locator Tests', () => { .sort((a, b) => a.executable.filename.localeCompare(b.executable.filename)); assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); - const expected = getExpectedPyenvInfo('3.9.0'); - - const actual = await locator.resolveEnv(pythonPath); - assertEnvEqual(actual, expected); - }); - test('resolveEnv(PythonEnvInfo)', async () => { - const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); - const expected = getExpectedPyenvInfo('3.9.0'); - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: platformUtils.Architecture.Unknown, - executable: { - filename: pythonPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: { - major: -1, - minor: -1, - micro: -1, - }, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); }); From 2062b0d8cc44971fcee41e7dc8d7625062c314d0 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 17:17:27 -0700 Subject: [PATCH 06/24] Move resolveEnv from windows store locator --- .../base/locators/composite/resolverUtils.ts | 7 +- .../locators/services/windowsStoreLocator.ts | 20 +--- .../composite/resolverUtils.unit.test.ts | 109 +++++++++++++++--- .../locators/windowsStoreLocator.unit.test.ts | 85 -------------- 4 files changed, 99 insertions(+), 122 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 9833f1e9dc0a..340a89b24554 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -16,6 +16,7 @@ import { getFileInfo, getWorkspaceFolders, isParentPath } from '../../../common/ import { AnacondaCompanyName, Conda } from '../../../discovery/locators/services/conda'; import { parsePyenvVersion } from '../../../discovery/locators/services/pyenvLocator'; import { Architecture } from '../../../../common/utils/platform'; +import { getPythonVersionFromPath as parsePythonVersionFromPath } from '../../info/pythonVersion'; export async function resolveEnv(executablePath: string): Promise { const resolved = await doResolveEnv(executablePath); @@ -46,7 +47,7 @@ async function doResolveEnv(executablePath: string): Promise { +export async function _resolveWindowsStoreEnv(executablePath: string): Promise { return buildEnvInfo({ kind: PythonEnvKind.WindowsStore, executable: executablePath, - version: await getPythonVersionFromPath(executablePath), + version: parsePythonVersionFromPath(executablePath), org: 'Microsoft', arch: Architecture.x64, fileInfo: await getFileInfo(executablePath), diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts index 48b6a70f0f80..8f1f03eca315 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts @@ -6,7 +6,7 @@ import * as minimatch from 'minimatch'; import * as path from 'path'; import { traceWarning } from '../../../../common/logger'; import { Architecture, getEnvironmentVariable } from '../../../../common/utils/platform'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/info'; +import { PythonEnvKind, PythonEnvSource } from '../../../base/info'; import { buildEnvInfo } from '../../../base/info/env'; import { getPythonVersionFromPath } from '../../../base/info/pythonVersion'; import { IPythonEnvsIterator } from '../../../base/locator'; @@ -229,22 +229,4 @@ export class WindowsStoreLocator extends FSWatchingLocator { }; return iterator(this.kind); } - - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - const source = - typeof env === 'string' ? [PythonEnvSource.PathEnvVar] : [PythonEnvSource.PathEnvVar, ...env.source]; - if (await isWindowsStoreEnvironment(executablePath)) { - return buildEnvInfo({ - kind: this.kind, - executable: executablePath, - version: getPythonVersionFromPath(executablePath), - org: 'Microsoft', - arch: Architecture.x64, - fileInfo: await getFileInfo(executablePath), - source, - }); - } - return undefined; - } } diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 1aa68e3154eb..a960edc26847 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -2,18 +2,29 @@ // Licensed under the MIT License. import * as path from 'path'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../../../client/pythonEnvironments/base/info'; +import { Architecture } from '../../../../../client/common/utils/platform'; +import { + PythonEnvInfo, + PythonEnvKind, + PythonEnvSource, + PythonVersion, + UNKNOWN_PYTHON_VERSION, +} from '../../../../../client/pythonEnvironments/base/info'; import { buildEnvInfo } from '../../../../../client/pythonEnvironments/base/info/env'; -import { _resolvePyenvEnv } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; +import { InterpreterInformation } from '../../../../../client/pythonEnvironments/base/info/interpreter'; +import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; +import { + _resolvePyenvEnv, + _resolveWindowsStoreEnv, +} from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { assertEnvEqual } from '../../../discovery/locators/envTestUtils'; suite('Resolver Utils', () => { - const testPyenvRoot = path.join(TEST_LAYOUT_ROOT, 'pyenvhome', '.pyenv'); - const testPyenvVersionsDir = path.join(testPyenvRoot, 'versions'); - - function getExpectedPyenvInfo(name: string): PythonEnvInfo | undefined { - if (name === '3.9.0') { + suite('Pyenv', () => { + const testPyenvRoot = path.join(TEST_LAYOUT_ROOT, 'pyenvhome', '.pyenv'); + const testPyenvVersionsDir = path.join(testPyenvRoot, 'versions'); + function getExpectedPyenvInfo(): PythonEnvInfo | undefined { const envInfo = buildEnvInfo({ kind: PythonEnvKind.Pyenv, executable: path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'), @@ -29,13 +40,81 @@ suite('Resolver Utils', () => { envInfo.name = '3.9.0'; return envInfo; } - return undefined; - } - test('resolvePyenvEnv', async () => { - const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); - const expected = getExpectedPyenvInfo('3.9.0'); - - const actual = await _resolvePyenvEnv(pythonPath); - assertEnvEqual(actual, expected); + + test('resolveEnv', async () => { + const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); + const expected = getExpectedPyenvInfo(); + + const actual = await _resolvePyenvEnv(pythonPath); + assertEnvEqual(actual, expected); + }); + }); + + suite('Windows store', () => { + const testLocalAppData = path.join(TEST_LAYOUT_ROOT, 'storeApps'); + const testStoreAppRoot = path.join(testLocalAppData, 'Microsoft', 'WindowsApps'); + + function createExpectedInterpreterInfo( + executable: string, + sysVersion?: string, + sysPrefix?: string, + versionStr?: string, + ): InterpreterInformation { + let version: PythonVersion; + try { + version = parseVersion(versionStr ?? path.basename(executable)); + if (sysVersion) { + version.sysVersion = sysVersion; + } + } catch (e) { + version = UNKNOWN_PYTHON_VERSION; + } + return { + version, + arch: Architecture.x64, + executable: { + filename: executable, + sysPrefix: sysPrefix ?? '', + ctime: -1, + mtime: -1, + }, + }; + } + + test('resolveEnv', async () => { + const python38path = path.join(testStoreAppRoot, 'python3.8.exe'); + const expected = { + display: undefined, + searchLocation: undefined, + name: '', + location: '', + kind: PythonEnvKind.WindowsStore, + distro: { org: 'Microsoft' }, + source: [PythonEnvSource.PathEnvVar], + ...createExpectedInterpreterInfo(python38path), + }; + + const actual = await _resolveWindowsStoreEnv(python38path); + + assertEnvEqual(actual, expected); + }); + + test('resolveEnv(string): forbidden path', async () => { + const python38path = path.join(testLocalAppData, 'Program Files', 'WindowsApps', 'python3.8.exe'); + const expected = { + display: undefined, + searchLocation: undefined, + name: '', + location: '', + kind: PythonEnvKind.WindowsStore, + distro: { org: 'Microsoft' }, + source: [PythonEnvSource.PathEnvVar], + ...createExpectedInterpreterInfo(python38path), + }; + + const actual = await _resolveWindowsStoreEnv(python38path); + + assertEnvEqual(actual, expected); + }); }); }); diff --git a/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts index 995082bb4192..31767da0d3e6 100644 --- a/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts @@ -183,90 +183,5 @@ suite('Windows Store', () => { assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const python38path = path.join(testStoreAppRoot, 'python3.8.exe'); - const expected = { - display: undefined, - searchLocation: undefined, - name: '', - location: '', - kind: PythonEnvKind.WindowsStore, - distro: { org: 'Microsoft' }, - source: [PythonEnvSource.PathEnvVar], - ...createExpectedInterpreterInfo(python38path), - }; - - const actual = await locator.resolveEnv(python38path); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const python38path = path.join(testStoreAppRoot, 'python3.8.exe'); - const expected = { - display: undefined, - searchLocation: undefined, - name: '', - location: '', - kind: PythonEnvKind.WindowsStore, - distro: { org: 'Microsoft' }, - source: [PythonEnvSource.PathEnvVar], - ...createExpectedInterpreterInfo(python38path), - }; - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - display: undefined, - searchLocation: undefined, - kind: PythonEnvKind.WindowsStore, - distro: { org: 'Microsoft' }, - arch: platformApis.Architecture.x64, - executable: { - filename: python38path, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: { - major: 3, - minor: -1, - micro: -1, - release: { level: PythonReleaseLevel.Final, serial: -1 }, - }, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); - test('resolveEnv(string): forbidden path', async () => { - const python38path = path.join(testLocalAppData, 'Program Files', 'WindowsApps', 'python3.8.exe'); - const expected = { - display: undefined, - searchLocation: undefined, - name: '', - location: '', - kind: PythonEnvKind.WindowsStore, - distro: { org: 'Microsoft' }, - source: [PythonEnvSource.PathEnvVar], - ...createExpectedInterpreterInfo(python38path), - }; - - const actual = await locator.resolveEnv(python38path); - - assertEnvEqual(actual, expected); - }); - test('resolveEnv(string): Non store python', async () => { - // Use a non store root path - const python38path = path.join(testLocalAppData, 'python3.8.exe'); - - const actual = await locator.resolveEnv(python38path); - - assert.deepStrictEqual(actual, undefined); - }); }); }); From 1fc2cb5b54fae1a22441709ed8d742c8f6f79c81 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 19:46:12 -0700 Subject: [PATCH 07/24] Add tests for conda resolver --- .../base/locators/composite/resolverUtils.ts | 4 +- .../composite/resolverUtils.unit.test.ts | 55 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 340a89b24554..7d6f28bc5272 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -43,7 +43,7 @@ async function doResolveEnv(executablePath: string): Promise { +export async function _resolveCondaEnv(env: string): Promise { const conda = await Conda.getConda(); if (conda === undefined) { traceVerbose(`Couldn't locate the conda binary in resolver`); diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index a960edc26847..0022e589dd4a 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -2,7 +2,8 @@ // Licensed under the MIT License. import * as path from 'path'; -import { Architecture } from '../../../../../client/common/utils/platform'; +import * as sinon from 'sinon'; +import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; import { PythonEnvInfo, PythonEnvKind, @@ -14,11 +15,17 @@ import { buildEnvInfo } from '../../../../../client/pythonEnvironments/base/info import { InterpreterInformation } from '../../../../../client/pythonEnvironments/base/info/interpreter'; import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; import { + _resolveCondaEnv, _resolvePyenvEnv, _resolveWindowsStoreEnv, } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { assertEnvEqual } from '../../../discovery/locators/envTestUtils'; +import { Architecture } from '../../../../../client/common/utils/platform'; +import { + AnacondaCompanyName, + CondaInfo, +} from '../../../../../client/pythonEnvironments/discovery/locators/services/conda'; suite('Resolver Utils', () => { suite('Pyenv', () => { @@ -117,4 +124,50 @@ suite('Resolver Utils', () => { assertEnvEqual(actual, expected); }); }); + + suite('Conda', () => { + const condaPrefix = path.join(TEST_LAYOUT_ROOT, 'conda1'); + function condaInfo(): CondaInfo { + return { + conda_version: '4.8.0', + python_version: '3.9.0', + 'sys.version': '3.9.0', + 'sys.prefix': '/some/env', + root_prefix: condaPrefix, + envs: [condaPrefix], + }; + } + + function expectedEnvInfo() { + const info = buildEnvInfo({ + executable: path.join(condaPrefix, 'python.exe'), + kind: PythonEnvKind.Conda, + org: AnacondaCompanyName, + location: condaPrefix, + source: [PythonEnvSource.Conda], + version: UNKNOWN_PYTHON_VERSION, + fileInfo: undefined, + name: 'base', + }); + return info; + } + + setup(() => { + sinon.stub(externalDependencies, 'exec').callsFake(async (command: string, args: string[]) => { + if (command === 'conda' && args[0] === 'info' && args[1] === '--json') { + return { stdout: JSON.stringify(condaInfo()) }; + } + throw new Error(`${command} is missing or is not executable`); + }); + }); + + teardown(() => { + sinon.restore(); + }); + + test('resolveEnv', async () => { + const actual = await _resolveCondaEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); + assertEnvEqual(actual, expectedEnvInfo()); + }); + }); }); From 158b8df686ffaf8bf550d7361cff327e6f2f2d72 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 19:47:13 -0700 Subject: [PATCH 08/24] Remove resolveEnv from conda locator --- .../discovery/locators/services/condaLocator.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts index b1d77b64d20b..cad35e89928d 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts @@ -63,16 +63,4 @@ export class CondaEnvironmentLocator extends Locator { } } } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - if (typeof env !== 'string') { - if (env.kind !== PythonEnvKind.Conda && env.kind !== PythonEnvKind.Unknown) { - return undefined; - } - } - - // There's no performance difference between getting all known environments, or just one - - // we have to spawn conda either way - so use the naive implementation. - return resolveEnvFromIterator(env, this.iterEnvs()); - } } From aaafc669f1981a8cb90b52934c57ee296bf2cc39 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 19:57:17 -0700 Subject: [PATCH 09/24] Fix compile errors --- .../locators/services/condaLocator.ts | 3 +- .../workspaceVirtualEnvLocator.unit.test.ts | 65 +------ .../customVirtualEnvLocator.unit.test.ts | 57 +----- ...rtualEnvironmentLocator.functional.test.ts | 60 +------ .../discovery/locators/index.unit.test.ts | 165 ------------------ .../locators/poetryLocator.unit.test.ts | 71 +------- .../posixKnownPathsLocator.unit.test.ts | 41 +---- .../locators/windowsStoreLocator.unit.test.ts | 3 +- 8 files changed, 7 insertions(+), 458 deletions(-) diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts index cad35e89928d..2d00db610c5b 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import '../../../../common/extensions'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/info'; +import { PythonEnvKind, PythonEnvSource } from '../../../base/info'; import { buildEnvInfo } from '../../../base/info/env'; import { IPythonEnvsIterator, Locator } from '../../../base/locator'; import { getInterpreterPathFromDir, getPythonVersionFromPath } from '../../../common/commonUtils'; import { AnacondaCompanyName, Conda } from './conda'; -import { resolveEnvFromIterator } from '../../../base/locatorUtils'; import { traceError, traceVerbose } from '../../../../common/logger'; export class CondaEnvironmentLocator extends Locator { diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts index 07f4e7287e0d..f99b32306cd4 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.unit.test.ts @@ -18,7 +18,7 @@ import { import { WorkspaceVirtualEnvironmentLocator } from '../../../../../client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator'; import { getEnvs } from '../../../../../client/pythonEnvironments/base/locatorUtils'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from '../../../discovery/locators/envTestUtils'; +import { assertEnvsEqual } from '../../../discovery/locators/envTestUtils'; suite('WorkspaceVirtualEnvironment Locator', () => { const testWorkspaceFolder = path.join(TEST_LAYOUT_ROOT, 'workspace', 'folder1'); @@ -139,67 +139,4 @@ suite('WorkspaceVirtualEnvironment Locator', () => { comparePaths(actualEnvs, expectedEnvs); assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const interpreterPath = path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv', 'bin', 'python'); - const expected = createExpectedEnvInfo( - path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv', 'bin', 'python'), - PythonEnvKind.VirtualEnv, - { major: 3, minor: 8, micro: -1 }, - 'posix1virtualenv', - path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv'), - ); - - const actual = await locator.resolveEnv(interpreterPath); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const interpreterPath = path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv', 'bin', 'python'); - const expected = createExpectedEnvInfo( - path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv', 'bin', 'python'), - PythonEnvKind.VirtualEnv, - { major: 3, minor: 8, micro: -1 }, - 'posix1virtualenv', - path.join(testWorkspaceFolder, '.direnv', 'posix1virtualenv'), - ); - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: platformUtils.Architecture.Unknown, - executable: { - filename: interpreterPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: UNKNOWN_PYTHON_VERSION, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(string): existing environment outside the root', async () => { - const interpreterPath = path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project1', '.venv', 'Scripts', 'python.exe'); - - const actual = await locator.resolveEnv(interpreterPath); - - assert.deepStrictEqual(actual, undefined); - }); - - test('resolveEnv(string): non existent path', async () => { - const interpreterPath = path.join(testWorkspaceFolder, 'some', 'random', 'nonvenv', 'python'); - - const actual = await locator.resolveEnv(interpreterPath); - - assert.deepStrictEqual(actual, undefined); - }); }); diff --git a/src/test/pythonEnvironments/discovery/locators/customVirtualEnvLocator.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/customVirtualEnvLocator.unit.test.ts index 2aefbbe19d74..1fe7326ad2f5 100644 --- a/src/test/pythonEnvironments/discovery/locators/customVirtualEnvLocator.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/customVirtualEnvLocator.unit.test.ts @@ -23,7 +23,7 @@ import { VENVPATH_SETTING_KEY, } from '../../../../client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; suite('CustomVirtualEnvironment Locator', () => { const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); @@ -375,61 +375,6 @@ suite('CustomVirtualEnvironment Locator', () => { assertEnvsEqual(actualEnvs, expectedEnvs); }); - test('resolveEnv(string)', async () => { - const interpreterPath = path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python'); - const expected = createExpectedEnvInfo( - path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python'), - PythonEnvKind.Venv, - undefined, - 'posix1', - path.join(testVirtualHomeDir, '.venvs', 'posix1'), - ); - - const actual = await locator.resolveEnv(interpreterPath); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const interpreterPath = path.join(testVirtualHomeDir, 'customfolder', 'posix1', 'python'); - const expected = createExpectedEnvInfo( - path.join(testVirtualHomeDir, 'customfolder', 'posix1', 'python'), - PythonEnvKind.VirtualEnv, - { major: 3, minor: 5, micro: -1 }, - 'posix1', - path.join(testVirtualHomeDir, 'customfolder', 'posix1'), - ); - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: platformUtils.Architecture.Unknown, - executable: { - filename: interpreterPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: UNKNOWN_PYTHON_VERSION, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(string): non existent path', async () => { - const interpreterPath = path.join('some', 'random', 'nonvenv', 'python'); - - const actual = await locator.resolveEnv(interpreterPath); - - assert.deepStrictEqual(actual, undefined); - }); - test('onChanged fires if venvPath setting changes', async () => { const events: PythonEnvsChangedEvent[] = []; const expected: PythonEnvsChangedEvent[] = [{}]; diff --git a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.functional.test.ts b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.functional.test.ts index ed9a6dc51c99..89da17a0171a 100644 --- a/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.functional.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/globalVirtualEnvironmentLocator.functional.test.ts @@ -18,7 +18,7 @@ import { getEnvs } from '../../../../client/pythonEnvironments/base/locatorUtils import * as externalDependencies from '../../../../client/pythonEnvironments/common/externalDependencies'; import { GlobalVirtualEnvironmentLocator } from '../../../../client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; suite('GlobalVirtualEnvironment Locator', () => { const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); @@ -459,62 +459,4 @@ suite('GlobalVirtualEnvironment Locator', () => { comparePaths(actualEnvs, expectedEnvs); assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const interpreterPath = path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python'); - const expected = createExpectedEnvInfo( - path.join(testVirtualHomeDir, '.venvs', 'posix1', 'python'), - PythonEnvKind.Venv, - undefined, - 'posix1', - path.join(testVirtualHomeDir, '.venvs', 'posix1'), - ); - - locator = new GlobalVirtualEnvironmentLocator(); - const actual = await locator.resolveEnv(interpreterPath); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const interpreterPath = path.join(testVirtualHomeDir, 'workonhome', 'posix1', 'python'); - const expected = createExpectedEnvInfo( - path.join(testVirtualHomeDir, 'workonhome', 'posix1', 'python'), - PythonEnvKind.VirtualEnvWrapper, - { major: 3, minor: 5, micro: -1 }, - 'posix1', - path.join(testVirtualHomeDir, 'workonhome', 'posix1'), - ); - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: platformUtils.Architecture.Unknown, - executable: { - filename: interpreterPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: UNKNOWN_PYTHON_VERSION, - source: [], - }; - - locator = new GlobalVirtualEnvironmentLocator(); - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(string): non existent path', async () => { - const interpreterPath = path.join('some', 'random', 'nonvenv', 'python'); - - locator = new GlobalVirtualEnvironmentLocator(); - const actual = await locator.resolveEnv(interpreterPath); - - assert.deepStrictEqual(actual, undefined); - }); }); diff --git a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts index 34843c5d60c1..81abb89ff3ac 100644 --- a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts @@ -613,171 +613,6 @@ suite('WorkspaceLocators', () => { assertSameEnvs(envsAfter, expectedAfter); }); }); - - suite('resolveEnv()', () => { - function getResolver(seen: number[], id: number, match = true) { - return async (env: PythonEnvInfo) => { - seen.push(id); - return match ? env : undefined; - }; - } - - test('no roots', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.equal(undefined, 'failed'); - }); - - test('no factories', async () => { - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.equal(undefined, 'failed'); - }); - - test('one locator, not resolved', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1], { resolve: null }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.equal(undefined, 'failed'); - }); - - test('one locator, resolved', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.deep.equal(expected); - }); - - test('one root, first locator resolves', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const seen: number[] = []; - const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2) }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1, loc2]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.deep.equal(expected); - expect(seen).to.deep.equal([1]); - }); - - test('one root, second locator resolves', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const seen: number[] = []; - const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1, false) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2) }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1, loc2]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.deep.equal(expected); - expect(seen).to.deep.equal([1, 2]); - }); - - test('one root, not resolved', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const seen: number[] = []; - const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1, false) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2, false) }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1, loc2]]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.equal(undefined, 'failed'); - expect(seen).to.deep.equal([1, 2]); - }); - - test('many roots, no searchLocation, second root matches', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected = env1; - const seen: number[] = []; - const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1, false) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2) }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [(r) => (r === root1 ? [loc1] : [loc2])]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.deep.equal(expected); - expect(seen).to.deep.equal([1, 2]); - }); - - test('many roots, searchLocation matches', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - env1.searchLocation = root2; - const expected = env1; - const seen: number[] = []; - const loc1 = new SimpleLocator([], { resolve: getResolver(seen, 1) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2) }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [(r) => (r === root1 ? [loc1] : [loc2])]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.deep.equal(expected); - expect(seen).to.deep.equal([2]); - }); - - test('many roots, searchLocation does not match', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - env1.searchLocation = Uri.file('baz'); - const expected = env1; - const seen: number[] = []; - const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1) }); - const loc2 = new SimpleLocator([], { resolve: getResolver(seen, 2) }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [(r) => (r === root1 ? [loc1] : [loc2])]); - await ensureActivated(locators); - - const resolved = await locators.resolveEnv(env1); - - expect(resolved).to.equal(expected); - expect(seen).to.deep.equal([1]); - }); - }); }); suite('Interpreters - Locators Index', () => { diff --git a/src/test/pythonEnvironments/discovery/locators/poetryLocator.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/poetryLocator.unit.test.ts index b369ed4fe1b5..968aab4e9468 100644 --- a/src/test/pythonEnvironments/discovery/locators/poetryLocator.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/poetryLocator.unit.test.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import * as sinon from 'sinon'; import { Uri } from 'vscode'; -import { assert } from 'chai'; import { PythonEnvInfo, PythonEnvKind, @@ -18,7 +17,7 @@ import * as platformUtils from '../../../../client/common/utils/platform'; import { getEnvs } from '../../../../client/pythonEnvironments/base/locatorUtils'; import { PoetryLocator } from '../../../../client/pythonEnvironments/discovery/locators/services/poetryLocator'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; import { ExecutionResult, ShellOptions } from '../../../../client/common/process/types'; import { Poetry } from '../../../../client/pythonEnvironments/discovery/locators/services/poetry'; @@ -132,74 +131,6 @@ suite('Poetry Locator', () => { ].sort((a, b) => a.executable.filename.localeCompare(b.executable.filename)); assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const interpreterPath = path.join(project1, '.venv', 'Scripts', 'python.exe'); - const expected = createExpectedEnvInfo( - path.join(project1, '.venv', 'Scripts', 'python.exe'), - PythonEnvKind.Poetry, - { - major: 3, - minor: 8, - micro: 2, - release: { level: PythonReleaseLevel.Final, serial: 0 }, - sysVersion: undefined, - }, - '.venv', - path.join(project1, '.venv'), - Uri.file(project1), - ); - - const actual = await locator.resolveEnv(interpreterPath); - - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const interpreterPath = path.join(project1, '.venv', 'Scripts', 'python.exe'); - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: platformUtils.Architecture.Unknown, - executable: { - filename: interpreterPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: UNKNOWN_PYTHON_VERSION, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - const expected = createExpectedEnvInfo( - path.join(project1, '.venv', 'Scripts', 'python.exe'), - PythonEnvKind.Poetry, - { - major: 3, - minor: 8, - micro: 2, - release: { level: PythonReleaseLevel.Final, serial: 0 }, - sysVersion: undefined, - }, - '.venv', - path.join(project1, '.venv'), - Uri.file(project1), - ); - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(string): non existent path', async () => { - const interpreterPath = path.join('some', 'random', 'nonvenv', 'python'); - - const actual = await locator.resolveEnv(interpreterPath); - - assert.deepStrictEqual(actual, undefined); - }); }); suite('Non-Windows', () => { diff --git a/src/test/pythonEnvironments/discovery/locators/posixKnownPathsLocator.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/posixKnownPathsLocator.unit.test.ts index 9cabf699a4a3..c4d5c39dea9c 100644 --- a/src/test/pythonEnvironments/discovery/locators/posixKnownPathsLocator.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/posixKnownPathsLocator.unit.test.ts @@ -17,7 +17,7 @@ import { parseVersion } from '../../../../client/pythonEnvironments/base/info/py import { getEnvs } from '../../../../client/pythonEnvironments/base/locatorUtils'; import { PosixKnownPathsLocator } from '../../../../client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; suite('Posix Known Path Locator', () => { let getPathEnvVar: sinon.SinonStub; @@ -88,43 +88,4 @@ suite('Posix Known Path Locator', () => { .sort((a, b) => a.executable.filename.localeCompare(b.executable.filename)); assertEnvsEqual(actualEnvs, expectedEnvs); }); - - test('resolveEnv(string)', async () => { - const pythonPath = path.join(testLocation1, 'python'); - const expected = createExpectedEnvInfo(pythonPath); - - const actual = await locator.resolveEnv(pythonPath); - assertEnvEqual(actual, expected); - }); - - test('resolveEnv(PythonEnvInfo)', async () => { - const pythonPath = path.join(testLocation1, 'python'); - const expected = createExpectedEnvInfo(pythonPath); - - // Partially filled in env info object - const input: PythonEnvInfo = { - name: '', - location: '', - kind: PythonEnvKind.Unknown, - distro: { org: '' }, - arch: Architecture.Unknown, - executable: { - filename: pythonPath, - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - version: { - major: -1, - minor: -1, - micro: -1, - release: { level: PythonReleaseLevel.Final, serial: -1 }, - }, - source: [], - }; - - const actual = await locator.resolveEnv(input); - - assertEnvEqual(actual, expected); - }); }); diff --git a/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts index 31767da0d3e6..1c6eb118ae4c 100644 --- a/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts @@ -11,7 +11,6 @@ import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, - PythonReleaseLevel, PythonVersion, UNKNOWN_PYTHON_VERSION, } from '../../../../client/pythonEnvironments/base/info'; @@ -25,7 +24,7 @@ import { } from '../../../../client/pythonEnvironments/discovery/locators/services/windowsStoreLocator'; import { getEnvs } from '../../base/common'; import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; -import { assertEnvEqual, assertEnvsEqual } from './envTestUtils'; +import { assertEnvsEqual } from './envTestUtils'; suite('Windows Store', () => { suite('Utils', () => { From b51288b64387cdd24d66a6d62430b065b67535c9 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Jul 2021 22:51:43 -0700 Subject: [PATCH 10/24] Do not skip virtualenv tests --- .../terminalActivation.testvirtualenvs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts b/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts index 8a3d7c44208d..275d628bbfd4 100644 --- a/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts +++ b/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts @@ -26,7 +26,7 @@ import { sleep } from '../../../core'; import { initialize, initializeTest } from '../../../initialize'; import * as ExperimentHelpers from '../../../../client/common/experiments/helpers'; -suite.skip('Activation of Environments in Terminal', () => { +suite('Activation of Environments in Terminal', () => { const file = path.join( EXTENSION_ROOT_DIR_FOR_TESTS, 'src', From 5ca786de7eac45b95386c577aba24f515de62a0d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 6 Jul 2021 14:44:28 -0700 Subject: [PATCH 11/24] Add tests for simple env case + Modify tests to use the general API instead --- .../base/locators/composite/resolverUtils.ts | 12 +-- .../composite/resolverUtils.unit.test.ts | 90 +++++++++++++++++-- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 7d6f28bc5272..3441e0782038 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -43,11 +43,11 @@ async function doResolveEnv(executablePath: string): Promise { +async function resolveCondaEnv(env: string): Promise { const conda = await Conda.getConda(); if (conda === undefined) { traceVerbose(`Couldn't locate the conda binary in resolver`); @@ -101,7 +101,7 @@ export async function _resolveCondaEnv(env: string): Promise { +async function resolvePyenvEnv(executablePath: string): Promise { const location = getEnvironmentDirFromPath(executablePath); const name = path.basename(location); @@ -122,7 +122,7 @@ export async function _resolvePyenvEnv(executablePath: string): Promise { +async function resolveWindowsStoreEnv(executablePath: string): Promise { return buildEnvInfo({ kind: PythonEnvKind.WindowsStore, executable: executablePath, diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 0022e589dd4a..30e5149cd417 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -3,7 +3,9 @@ import * as path from 'path'; import * as sinon from 'sinon'; +import { Uri } from 'vscode'; import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; +import * as platformApis from '../../../../../client/common/utils/platform'; import { PythonEnvInfo, PythonEnvKind, @@ -14,11 +16,7 @@ import { import { buildEnvInfo } from '../../../../../client/pythonEnvironments/base/info/env'; import { InterpreterInformation } from '../../../../../client/pythonEnvironments/base/info/interpreter'; import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; -import { - _resolveCondaEnv, - _resolvePyenvEnv, - _resolveWindowsStoreEnv, -} from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; +import { resolveEnv } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; import { assertEnvEqual } from '../../../discovery/locators/envTestUtils'; import { Architecture } from '../../../../../client/common/utils/platform'; @@ -31,6 +29,14 @@ suite('Resolver Utils', () => { suite('Pyenv', () => { const testPyenvRoot = path.join(TEST_LAYOUT_ROOT, 'pyenvhome', '.pyenv'); const testPyenvVersionsDir = path.join(testPyenvRoot, 'versions'); + setup(() => { + sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); + sinon.stub(platformApis, 'getEnvironmentVariable').withArgs('PYENV_ROOT').returns(testPyenvRoot); + }); + + teardown(() => { + sinon.restore(); + }); function getExpectedPyenvInfo(): PythonEnvInfo | undefined { const envInfo = buildEnvInfo({ kind: PythonEnvKind.Pyenv, @@ -52,7 +58,7 @@ suite('Resolver Utils', () => { const pythonPath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); const expected = getExpectedPyenvInfo(); - const actual = await _resolvePyenvEnv(pythonPath); + const actual = await resolveEnv(pythonPath); assertEnvEqual(actual, expected); }); }); @@ -61,6 +67,15 @@ suite('Resolver Utils', () => { const testLocalAppData = path.join(TEST_LAYOUT_ROOT, 'storeApps'); const testStoreAppRoot = path.join(testLocalAppData, 'Microsoft', 'WindowsApps'); + setup(() => { + sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); + sinon.stub(platformApis, 'getEnvironmentVariable').withArgs('LOCALAPPDATA').returns(testLocalAppData); + }); + + teardown(() => { + sinon.restore(); + }); + function createExpectedInterpreterInfo( executable: string, sysVersion?: string, @@ -101,7 +116,7 @@ suite('Resolver Utils', () => { ...createExpectedInterpreterInfo(python38path), }; - const actual = await _resolveWindowsStoreEnv(python38path); + const actual = await resolveEnv(python38path); assertEnvEqual(actual, expected); }); @@ -119,7 +134,7 @@ suite('Resolver Utils', () => { ...createExpectedInterpreterInfo(python38path), }; - const actual = await _resolveWindowsStoreEnv(python38path); + const actual = await resolveEnv(python38path); assertEnvEqual(actual, expected); }); @@ -152,6 +167,14 @@ suite('Resolver Utils', () => { return info; } + suiteSetup(() => { + sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); + }); + + suiteTeardown(() => { + sinon.restore(); + }); + setup(() => { sinon.stub(externalDependencies, 'exec').callsFake(async (command: string, args: string[]) => { if (command === 'conda' && args[0] === 'info' && args[1] === '--json') { @@ -166,8 +189,57 @@ suite('Resolver Utils', () => { }); test('resolveEnv', async () => { - const actual = await _resolveCondaEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); + const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); assertEnvEqual(actual, expectedEnvInfo()); }); }); + + suite('Simple envs', () => { + const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); + suiteSetup(() => { + sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([testVirtualHomeDir]); + }); + + suiteTeardown(() => { + sinon.restore(); + }); + + function createExpectedEnvInfo( + interpreterPath: string, + kind: PythonEnvKind, + version: PythonVersion = UNKNOWN_PYTHON_VERSION, + name = '', + location = '', + ): PythonEnvInfo { + return { + name, + location, + kind, + executable: { + filename: interpreterPath, + sysPrefix: '', + ctime: -1, + mtime: -1, + }, + display: undefined, + version, + arch: Architecture.Unknown, + distro: { org: '' }, + searchLocation: Uri.file(path.dirname(location)), + source: [PythonEnvSource.Other], + }; + } + + test('resolveEnv', async () => { + const expected = createExpectedEnvInfo( + path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), + PythonEnvKind.Venv, + undefined, + 'win1', + path.join(testVirtualHomeDir, '.venvs', 'win1'), + ); + const actual = await resolveEnv(path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe')); + assertEnvEqual(actual, expected); + }); + }); }); From 866bb707b90f80968ba4c6dd2a8302d73bd1a9bd Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 13:04:28 -0700 Subject: [PATCH 12/24] Change resolveEnv prototype to only accept strings --- src/client/interpreter/contracts.ts | 2 +- src/client/interpreter/interpreterService.ts | 3 +++ src/client/pythonEnvironments/api.ts | 2 +- src/client/pythonEnvironments/base/locator.ts | 2 +- .../locators/common/resourceBasedLocator.ts | 2 +- .../base/locators/common/wrappingLocator.ts | 2 +- .../base/locators/composite/cachingLocator.ts | 3 ++- src/client/pythonEnvironments/legacyIOC.ts | 18 ++++++------------ .../composite/cachingLocator.unit.test.ts | 15 +++------------ 9 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index cc2e386efbd8..dd21e0d20deb 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -54,7 +54,7 @@ export interface IComponentAdapter { // IInterpreterLocatorService (for WINDOWS_REGISTRY_SERVICE) getWinRegInterpreters(resource: Resource): Promise; // IInterpreterService - getInterpreterDetails(pythonPath: string, _resource?: Uri): Promise; + getInterpreterDetails(pythonPath: string): Promise; // IInterpreterHelper // Undefined is expected on this API, if the environment info retrieval fails. diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 50d78f980189..ea5da0d2aa44 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -205,6 +205,9 @@ export class InterpreterService implements Disposable, IInterpreterService { ): Promise { if (await inDiscoveryExperiment(this.experimentService)) { const info = await this.pyenvs.getInterpreterDetails(pythonPath); + if (!info) { + return undefined; + } if (!info.displayName) { // Set display name for the environment returned by component if it's not set (this should eventually go away) info.displayName = await this.getDisplayName(info, resource); diff --git a/src/client/pythonEnvironments/api.ts b/src/client/pythonEnvironments/api.ts index 487d512b4bd3..58626cba2360 100644 --- a/src/client/pythonEnvironments/api.ts +++ b/src/client/pythonEnvironments/api.ts @@ -41,7 +41,7 @@ export class PythonEnvironments implements IResolvingLocator, IDisposable { return this.locators.iterEnvs(query); } - public async resolveEnv(env: string | PythonEnvInfo): Promise { + public async resolveEnv(env: string): Promise { return this.locators.resolveEnv(env); } } diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 8dec41b0cb12..d0d8680fd240 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -164,7 +164,7 @@ interface IResolver { * * @param env - the Python executable path or partial env info to find and update */ - resolveEnv(env: string | PythonEnvInfo): Promise; + resolveEnv(env: string): Promise; } export interface IResolvingLocator extends IResolver, ILocator {} diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index 74735967c586..a1c397d11958 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -79,7 +79,7 @@ export abstract class LazyResourceBasedLocator extends Locator implements IDispo // No watchers! } - private async ensureResourcesReady(): Promise { + protected async ensureResourcesReady(): Promise { if (this.resourcesReady !== undefined) { await this.resourcesReady.promise; return; diff --git a/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts b/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts index 3a844d81ada6..7431fc217b77 100644 --- a/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/wrappingLocator.ts @@ -31,7 +31,7 @@ export class LazyWrappingLocator extends LazyResourceBasedLocator { yield* this.wrapped!.iterEnvs(query); } - public async resolveEnv(env: string | PythonEnvInfo): Promise { + public async resolveEnv(env: string): Promise { return this.wrapped!.resolveEnv(env); } diff --git a/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts b/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts index d18131696c16..caa46abf9e4f 100644 --- a/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/composite/cachingLocator.ts @@ -39,7 +39,8 @@ export class CachingLocator extends LazyResourceBasedLocator implements IResolvi yield* this.iterFromCache(query); } - public async resolveEnv(env: string | PythonEnvInfo): Promise { + public async resolveEnv(env: string): Promise { + await this.ensureResourcesReady(); let matchingEnvs = this.filterMatchingEnvsFromCache(env); if (matchingEnvs.length > 0) { return pickBestEnv(matchingEnvs); diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index b2463d306c2f..518b1a7373f2 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -35,7 +35,6 @@ import { VirtualEnvironmentManager } from '../interpreter/virtualEnvs'; import { IVirtualEnvironmentManager } from '../interpreter/virtualEnvs/types'; import { IServiceManager } from '../ioc/types'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './base/info'; -import { buildEnvInfo } from './base/info/env'; import { IResolvingLocator, PythonLocatorQuery } from './base/locator'; import { isMacDefaultPythonPath } from './base/locators/lowLevel/macDefaultLocator'; import { getEnvs } from './base/locatorUtils'; @@ -191,17 +190,12 @@ class ComponentAdapter implements IComponentAdapter { // Implements IInterpreterService // We use the same getInterpreters() here as for IInterpreterLocatorService. - public async getInterpreterDetails(pythonPath: string, resource?: vscode.Uri): Promise { - const envInfo = buildEnvInfo({ executable: pythonPath }); - if (resource !== undefined) { - const wsFolder = vscode.workspace.getWorkspaceFolder(resource); - if (wsFolder !== undefined) { - envInfo.searchLocation = wsFolder.uri; - } + public async getInterpreterDetails(pythonPath: string): Promise { + const env = await this.api.resolveEnv(pythonPath); + if (!env) { + return undefined; } - - const env = (await this.api.resolveEnv(envInfo)) ?? envInfo; - if (env.executable.sysPrefix) { + if (env?.executable.sysPrefix) { const execInfoService = getEnvironmentInfoService(); const info = await execInfoService.getEnvironmentInfo(pythonPath, EnvironmentInfoServiceQueuePriority.High); if (info) { @@ -209,7 +203,7 @@ class ComponentAdapter implements IComponentAdapter { env.version = info.version; } } - return convertEnvInfo(env ?? buildEnvInfo({ executable: pythonPath })); + return convertEnvInfo(env); } // Implements ICondaService diff --git a/src/test/pythonEnvironments/base/locators/composite/cachingLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/cachingLocator.unit.test.ts index 8b97759ac6cb..d3550bab7435 100644 --- a/src/test/pythonEnvironments/base/locators/composite/cachingLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/cachingLocator.unit.test.ts @@ -200,15 +200,6 @@ suite('Python envs locator - CachingLocator', () => { }); suite('resolveEnv()', () => { - test('full match in cache', async () => { - const expected = env5; - const [, locator] = await getInitializedLocator(envs); - - const resolved = await locator.resolveEnv(env5); - - assert.deepEqual(resolved, expected); - }); - test('executable match in cache', async () => { const expected = env5; const [, locator] = await getInitializedLocator(envs); @@ -225,7 +216,7 @@ suite('Python envs locator - CachingLocator', () => { const iterator1 = locator.iterEnvs(); const discoveredBefore = await getEnvs(iterator1); - const resolved = await locator.resolveEnv(env5); + const resolved = await locator.resolveEnv(env5.executable.filename); const iterator2 = locator.iterEnvs(); const discoveredAfter = await getEnvs(iterator2); @@ -244,7 +235,7 @@ suite('Python envs locator - CachingLocator', () => { const iterator1 = locator.iterEnvs(); const discoveredBefore = await getEnvs(iterator1); - const resolved = await locator.resolveEnv(env5); + const resolved = await locator.resolveEnv(env5.executable.filename); const iterator2 = locator.iterEnvs(); const discoveredAfter = await getEnvs(iterator2); @@ -257,7 +248,7 @@ suite('Python envs locator - CachingLocator', () => { test('not in cache nor downstream', async () => { const [, locator] = await getInitializedLocator([]); - const resolved = await locator.resolveEnv(env5); + const resolved = await locator.resolveEnv(env5.executable.filename); assert.equal(resolved, undefined); }); From af0bdf23ae7e726ffd094a51b5d9a010a3147b80 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 13:33:06 -0700 Subject: [PATCH 13/24] Add conda resolver test for Linux --- .../composite/resolverUtils.unit.test.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 30e5149cd417..1f81f6f8971b 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -141,8 +141,9 @@ suite('Resolver Utils', () => { }); suite('Conda', () => { - const condaPrefix = path.join(TEST_LAYOUT_ROOT, 'conda1'); - function condaInfo(): CondaInfo { + const condaPrefixNonWindows = path.join(TEST_LAYOUT_ROOT, 'conda2'); + const condaPrefixWindows = path.join(TEST_LAYOUT_ROOT, 'conda1'); + function condaInfo(condaPrefix: string): CondaInfo { return { conda_version: '4.8.0', python_version: '3.9.0', @@ -153,12 +154,12 @@ suite('Resolver Utils', () => { }; } - function expectedEnvInfo() { + function expectedEnvInfo(executable: string, location: string) { const info = buildEnvInfo({ - executable: path.join(condaPrefix, 'python.exe'), + executable, kind: PythonEnvKind.Conda, org: AnacondaCompanyName, - location: condaPrefix, + location, source: [PythonEnvSource.Conda], version: UNKNOWN_PYTHON_VERSION, fileInfo: undefined, @@ -167,7 +168,7 @@ suite('Resolver Utils', () => { return info; } - suiteSetup(() => { + setup(() => { sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); }); @@ -175,22 +176,35 @@ suite('Resolver Utils', () => { sinon.restore(); }); - setup(() => { + teardown(() => { + sinon.restore(); + }); + + test('resolveEnv (Windows)', async () => { + sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Windows); sinon.stub(externalDependencies, 'exec').callsFake(async (command: string, args: string[]) => { if (command === 'conda' && args[0] === 'info' && args[1] === '--json') { - return { stdout: JSON.stringify(condaInfo()) }; + return { stdout: JSON.stringify(condaInfo(condaPrefixWindows)) }; } throw new Error(`${command} is missing or is not executable`); }); + const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); + assertEnvEqual(actual, expectedEnvInfo(path.join(condaPrefixWindows, 'python.exe'), condaPrefixWindows)); }); - teardown(() => { - sinon.restore(); - }); - - test('resolveEnv', async () => { - const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); - assertEnvEqual(actual, expectedEnvInfo()); + test('resolveEnv (non-Windows)', async () => { + sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Linux); + sinon.stub(externalDependencies, 'exec').callsFake(async (command: string, args: string[]) => { + if (command === 'conda' && args[0] === 'info' && args[1] === '--json') { + return { stdout: JSON.stringify(condaInfo(condaPrefixNonWindows)) }; + } + throw new Error(`${command} is missing or is not executable`); + }); + const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda2', 'bin', 'python')); + assertEnvEqual( + actual, + expectedEnvInfo(path.join(condaPrefixNonWindows, 'bin', 'python'), condaPrefixNonWindows), + ); }); }); From 29eae66ffca697aa2615ed44b0f5b0e9dfd2bc7d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 16:04:28 -0700 Subject: [PATCH 14/24] Fix tests for resolver --- .../composite/environmentsResolver.ts | 3 +- .../base/locators/composite/resolverUtils.ts | 3 +- .../environmentsResolver.unit.test.ts | 110 +++++++++--------- .../composite/resolverUtils.unit.test.ts | 10 ++ 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index d2acacb5c1f9..b5b0b3359905 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -31,8 +31,7 @@ export class PythonEnvsResolver implements IResolvingLocator { private readonly environmentInfoService: IEnvironmentInfoService, ) {} - public async resolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; + public async resolveEnv(executablePath: string): Promise { const environment = await resolveEnv(executablePath); if (!environment) { return undefined; diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 3441e0782038..e3f3e4cc85ac 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; -import { traceVerbose } from '../../../../common/logger'; +import { traceError, traceVerbose } from '../../../../common/logger'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info'; import { buildEnvInfo, getEnvMatcher } from '../../info/env'; import { @@ -98,6 +98,7 @@ async function resolveCondaEnv(env: string): Promise return info; } } + traceError(`${env} identified as a Conda environment but is not returned via '${conda.command} info' command`); return undefined; } diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts index 2526ad27edc6..811053c4b5d4 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts @@ -6,11 +6,18 @@ import { cloneDeep } from 'lodash'; import * as path from 'path'; import * as sinon from 'sinon'; import { ImportMock } from 'ts-mock-imports'; -import { EventEmitter } from 'vscode'; +import { EventEmitter, Uri } from 'vscode'; import { ExecutionResult } from '../../../../../client/common/process/types'; import { IDisposableRegistry } from '../../../../../client/common/types'; import { Architecture } from '../../../../../client/common/utils/platform'; -import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; +import * as platformApis from '../../../../../client/common/utils/platform'; +import { + PythonEnvInfo, + PythonEnvKind, + PythonEnvSource, + PythonVersion, + UNKNOWN_PYTHON_VERSION, +} from '../../../../../client/pythonEnvironments/base/info'; import { parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator'; import { PythonEnvsResolver } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsResolver'; @@ -22,6 +29,8 @@ import { IEnvironmentInfoService, } from '../../../../../client/pythonEnvironments/info/environmentInfoService'; import { sleep } from '../../../../core'; +import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants'; +import { assertEnvEqual } from '../../../discovery/locators/envTestUtils'; import { createNamedEnv, getEnvs, SimpleLocator } from '../../common'; suite('Python envs locator - Environments Resolver', () => { @@ -203,7 +212,34 @@ suite('Python envs locator - Environments Resolver', () => { suite('resolveEnv()', () => { let stubShellExec: sinon.SinonStub; + const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); + function createExpectedResolvedEnvInfo( + interpreterPath: string, + kind: PythonEnvKind, + version: PythonVersion = UNKNOWN_PYTHON_VERSION, + name = '', + location = '', + ): PythonEnvInfo { + return { + name, + location, + kind, + executable: { + filename: interpreterPath, + sysPrefix: '', + ctime: -1, + mtime: -1, + }, + display: undefined, + version, + arch: Architecture.Unknown, + distro: { org: '' }, + searchLocation: Uri.file(path.dirname(location)), + source: [PythonEnvSource.Other], + }; + } setup(() => { + sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Linux); stubShellExec = ImportMock.mockFunction( ExternalDep, 'shellExecute', @@ -214,6 +250,7 @@ suite('Python envs locator - Environments Resolver', () => { }); }), ); + sinon.stub(ExternalDep, 'getWorkspaceFolders').returns([testVirtualHomeDir]); }); teardown(() => { @@ -221,26 +258,19 @@ suite('Python envs locator - Environments Resolver', () => { }); test('Calls into parent locator to get resolved environment, then calls environnment service to resolve environment further and return it', async () => { - const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const resolvedEnvReturnedByReducer = createNamedEnv( - 'env1', - '3.8.1', - PythonEnvKind.Conda, - 'resolved/path/to/exec', + const resolvedEnvReturnedByResolver = createExpectedResolvedEnvInfo( + path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), + PythonEnvKind.Venv, + undefined, + 'win1', + path.join(testVirtualHomeDir, '.venvs', 'win1'), ); - const parentLocator = new SimpleLocator([], { - resolve: async (e: PythonEnvInfo) => { - if (e === env) { - return resolvedEnvReturnedByReducer; - } - throw new Error('Incorrect environment sent to the resolver'); - }, - }); + const parentLocator = new SimpleLocator([]); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); - const expected = await resolver.resolveEnv(env); + const expected = await resolver.resolveEnv(path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe')); - assert.deepEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByReducer)); + assertEnvEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByResolver)); }); test('If the parent locator resolves environment, but running interpreter info throws error, return undefined', async () => { @@ -249,24 +279,10 @@ suite('Python envs locator - Environments Resolver', () => { reject(); }), ); - const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const resolvedEnvReturnedByReducer = createNamedEnv( - 'env1', - '3.8.1', - PythonEnvKind.Conda, - 'resolved/path/to/exec', - ); - const parentLocator = new SimpleLocator([], { - resolve: async (e: PythonEnvInfo) => { - if (e === env) { - return resolvedEnvReturnedByReducer; - } - throw new Error('Incorrect environment sent to the resolver'); - }, - }); + const parentLocator = new SimpleLocator([]); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); - const expected = await resolver.resolveEnv(env); + const expected = await resolver.resolveEnv(path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe')); assert.deepEqual(expected, undefined); }); @@ -280,36 +296,22 @@ suite('Python envs locator - Environments Resolver', () => { }); }), ); - const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const resolvedEnvReturnedByReducer = createNamedEnv( - 'env1', - '3.8.1', - PythonEnvKind.Conda, - 'resolved/path/to/exec', - ); - const parentLocator = new SimpleLocator([], { - resolve: async (e: PythonEnvInfo) => { - if (e === env) { - return resolvedEnvReturnedByReducer; - } - throw new Error('Incorrect environment sent to the resolver'); - }, - }); + const parentLocator = new SimpleLocator([]); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); - const expected = await resolver.resolveEnv(env); + const expected = await resolver.resolveEnv(path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe')); assert.deepEqual(expected, undefined); }); test("If the parent locator isn't able to resolve environment, return undefined", async () => { - const env = createNamedEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const parentLocator = new SimpleLocator([], { - resolve: async () => undefined, + sinon.stub(ExternalDep, 'exec').callsFake(async (command: string) => { + throw new Error(`Conda binary command ${command} is missing or is not executable`); }); + const parentLocator = new SimpleLocator([]); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); - const expected = await resolver.resolveEnv(env); + const expected = await resolver.resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); assert.deepEqual(expected, undefined); }); diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 1f81f6f8971b..a3c9f9670159 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as sinon from 'sinon'; import { Uri } from 'vscode'; +import { assert } from 'chai'; import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; import * as platformApis from '../../../../../client/common/utils/platform'; import { @@ -206,6 +207,15 @@ suite('Resolver Utils', () => { expectedEnvInfo(path.join(condaPrefixNonWindows, 'bin', 'python'), condaPrefixNonWindows), ); }); + + test('resolveEnv: No conda binary found', async () => { + sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Windows); + sinon.stub(externalDependencies, 'exec').callsFake(async (command: string) => { + throw new Error(`${command} is missing or is not executable`); + }); + const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); + assert.equal(actual, undefined); + }); }); suite('Simple envs', () => { From 0bd9ad85dff2372ea70ed5a47440fd732e72d54b Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 16:25:09 -0700 Subject: [PATCH 15/24] See if it fixes tests upstream --- .../pythonEnvironments/discovery/locators/services/conda.ts | 3 ++- .../terminalActivation.testvirtualenvs.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/pythonEnvironments/discovery/locators/services/conda.ts b/src/client/pythonEnvironments/discovery/locators/services/conda.ts index 91a17b2e4f77..a26663ca7200 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/conda.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/conda.ts @@ -11,6 +11,7 @@ import { getRegistryInterpreters } from '../../../common/windowsUtils'; import { EnvironmentType, PythonEnvironment } from '../../../info'; import { IDisposable } from '../../../../common/types'; import { cache } from '../../../../common/utils/decorators'; +import { isTestExecution } from '../../../../common/constants'; export const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.']; @@ -223,7 +224,7 @@ export class Conda { public static async getConda(): Promise { traceVerbose(`Searching for conda.`); - if (this.condaPromise === undefined) { + if (this.condaPromise === undefined || isTestExecution()) { this.condaPromise = Conda.locate(); } return this.condaPromise; diff --git a/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts b/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts index 275d628bbfd4..8a3d7c44208d 100644 --- a/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts +++ b/src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts @@ -26,7 +26,7 @@ import { sleep } from '../../../core'; import { initialize, initializeTest } from '../../../initialize'; import * as ExperimentHelpers from '../../../../client/common/experiments/helpers'; -suite('Activation of Environments in Terminal', () => { +suite.skip('Activation of Environments in Terminal', () => { const file = path.join( EXTENSION_ROOT_DIR_FOR_TESTS, 'src', From c3517da2edea049100c73909af0ebc77e360788b Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 16:34:16 -0700 Subject: [PATCH 16/24] Call Conda.locate() only once every session --- .../discovery/locators/services/conda.ts | 12 +++++----- .../locators/services/condaLocator.ts | 22 +++---------------- .../locators/services/condaService.ts | 22 +++---------------- .../locators/condaHelper.unit.test.ts | 4 ++-- 4 files changed, 15 insertions(+), 45 deletions(-) diff --git a/src/client/pythonEnvironments/discovery/locators/services/conda.ts b/src/client/pythonEnvironments/discovery/locators/services/conda.ts index a26663ca7200..7c10dd86a984 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/conda.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/conda.ts @@ -208,10 +208,12 @@ export async function getPythonVersionFromConda(interpreterPath: string): Promis /** Wraps the "conda" utility, and exposes its functionality. */ export class Conda { - // Locating conda binary is expensive, since it potentially involves spawning or - // trying to spawn processes; so it's done lazily and asynchronously. Methods that - // need a Conda instance should use getConda() to obtain it, and should never access - // this property directly. + /** + * Locating conda binary is expensive, since it potentially involves spawning or + * trying to spawn processes; so it's done lazily and asynchronously. Methods that + * need a Conda instance should use getConda() to obtain it, and should never access + * this property directly. + */ private static condaPromise: Promise | undefined; /** @@ -236,7 +238,7 @@ export class Conda { * * @return A Conda instance corresponding to the binary, if successful; otherwise, undefined. */ - public static async locate(): Promise { + private static async locate(): Promise { const home = getUserHomeDir(); const suffix = getOSType() === OSType.Windows ? 'Scripts\\conda.exe' : 'bin/conda'; diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts index 2d00db610c5b..d21844e2ad87 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts @@ -9,29 +9,13 @@ import { AnacondaCompanyName, Conda } from './conda'; import { traceError, traceVerbose } from '../../../../common/logger'; export class CondaEnvironmentLocator extends Locator { - // Locating conda binary is expensive, since it potentially involves spawning or - // trying to spawn processes; so it's done lazily and asynchronously. Methods that - // need a Conda instance should use getConda() to obtain it, and should never access - // this property directly. - private condaPromise: Promise | undefined; - - public constructor(conda?: Conda) { + public constructor() { super(); - if (conda !== undefined) { - this.condaPromise = Promise.resolve(conda); - } - } - - public async getConda(): Promise { - traceVerbose(`Searching for conda.`); - if (this.condaPromise === undefined) { - this.condaPromise = Conda.locate(); - } - return this.condaPromise; } + // eslint-disable-next-line class-methods-use-this public async *iterEnvs(): IPythonEnvsIterator { - const conda = await this.getConda(); + const conda = await Conda.getConda(); if (conda === undefined) { traceVerbose(`Couldn't locate the conda binary.`); return; diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts index a8d65aef3861..097ecddcecd6 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts @@ -4,7 +4,7 @@ import { parse, SemVer } from 'semver'; import { ConfigurationChangeEvent, Uri } from 'vscode'; import { IWorkspaceService } from '../../../../common/application/types'; import { inDiscoveryExperiment } from '../../../../common/experiments/helpers'; -import { traceDecorators, traceVerbose, traceWarning } from '../../../../common/logger'; +import { traceDecorators, traceWarning } from '../../../../common/logger'; import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; import { IProcessServiceFactory } from '../../../../common/process/types'; import { IExperimentService, IConfigurationService, IDisposableRegistry } from '../../../../common/types'; @@ -22,14 +22,6 @@ export class CondaService implements ICondaService { private condaFile: Promise | undefined; - /** - * Locating conda binary is expensive, since it potentially involves spawning or - * trying to spawn processes; so it's done lazily and asynchronously. Methods that - * need a Conda instance should use getConda() to obtain it, and should never access - * this property directly. - */ - private condaPromise: Promise | undefined; - constructor( @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, @inject(IPlatformService) private platform: IPlatformService, @@ -160,7 +152,7 @@ export class CondaService implements ICondaService { if (!(await inDiscoveryExperiment(this.experimentService))) { return this.serviceContainer.get(ICondaLocatorService).getCondaInfo(); } - const conda = await this.getConda(); + const conda = await Conda.getConda(); return conda?.getInfo(); } @@ -173,18 +165,10 @@ export class CondaService implements ICondaService { if (setting && setting !== '') { return setting; } - const conda = await this.getConda(); + const conda = await Conda.getConda(); return conda?.command ?? 'conda'; } - private async getConda(): Promise { - traceVerbose(`Searching for conda.`); - if (this.condaPromise === undefined) { - this.condaPromise = Conda.locate(); - } - return this.condaPromise; - } - private addCondaPathChangedHandler() { const disposable = this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); this.disposableRegistry.push(disposable); diff --git a/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts index 3607586f6433..c687daa34d00 100644 --- a/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/condaHelper.unit.test.ts @@ -169,7 +169,7 @@ suite('Conda and its environments are located correctly', () => { async function expectConda(expectedPath: string) { const expectedInfo = JSON.parse(getFile(expectedPath) as string); - const conda = await Conda.locate(); + const conda = await Conda.getConda(); expect(conda).to.not.equal(undefined, 'conda should not be missing'); const info = await conda!.getInfo(); @@ -287,7 +287,7 @@ suite('Conda and its environments are located correctly', () => { suite('Conda binary is located correctly', () => { test('Must not find conda if it is missing', async () => { - const conda = await Conda.locate(); + const conda = await Conda.getConda(); expect(conda).to.equal(undefined, 'conda should be missing'); }); From 6b8f0698cd223aa4badde53e6a3e0ab9582a91ab Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 16:39:19 -0700 Subject: [PATCH 17/24] Update doc description for resolveEnv API --- src/client/pythonEnvironments/base/locator.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index d0d8680fd240..7022791fa85f 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -151,18 +151,10 @@ export interface ILocator; } From cb646120e32bd54e271379a0cf795e352eecbdc5 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 17:20:37 -0700 Subject: [PATCH 18/24] If environment is identified as Conda but not retrieved via conda CLI, resolve it as simple env --- .../composite/environmentsResolver.ts | 3 -- .../base/locators/composite/resolverUtils.ts | 34 +++++++++------- .../environmentsResolver.unit.test.ts | 20 ++-------- .../composite/resolverUtils.unit.test.ts | 39 +++++++++++++++++-- 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index b5b0b3359905..70e0a99f5fd6 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -33,9 +33,6 @@ export class PythonEnvsResolver implements IResolvingLocator { public async resolveEnv(executablePath: string): Promise { const environment = await resolveEnv(executablePath); - if (!environment) { - return undefined; - } const info = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename); if (!info) { return undefined; diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index e3f3e4cc85ac..d81d89e81782 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; -import { traceError, traceVerbose } from '../../../../common/logger'; +import { traceError, traceWarning } from '../../../../common/logger'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info'; import { buildEnvInfo, getEnvMatcher } from '../../info/env'; import { @@ -18,7 +18,12 @@ import { parsePyenvVersion } from '../../../discovery/locators/services/pyenvLoc import { Architecture } from '../../../../common/utils/platform'; import { getPythonVersionFromPath as parsePythonVersionFromPath } from '../../info/pythonVersion'; -export async function resolveEnv(executablePath: string): Promise { +/** + * Find as much info about the given Python environment as possible without running the + * Python executable and returns it. Notice `undefined` is never returned, so environment + * returned could still be invalid. + */ +export async function resolveEnv(executablePath: string): Promise { const resolved = await doResolveEnv(executablePath); if (resolved) { const folders = getWorkspaceFolders(); @@ -39,7 +44,7 @@ export async function resolveEnv(executablePath: string): Promise { +async function doResolveEnv(executablePath: string): Promise { const kind = await identifyEnvironment(executablePath); switch (kind) { case PythonEnvKind.Conda: @@ -63,23 +68,19 @@ async function resolveSimpleEnv(executablePath: string, kind: PythonEnvKind): Pr const location = getEnvironmentDirFromPath(executablePath); envInfo.location = location; envInfo.name = path.basename(location); - - // TODO: Call a general display name provider here to build display name. const fileData = await getFileInfo(executablePath); envInfo.executable.ctime = fileData.ctime; envInfo.executable.mtime = fileData.mtime; return envInfo; } -async function resolveCondaEnv(env: string): Promise { +async function resolveCondaEnv(executablePath: string): Promise { const conda = await Conda.getConda(); if (conda === undefined) { - traceVerbose(`Couldn't locate the conda binary in resolver`); - return undefined; + traceWarning(`${executablePath} identified as Conda environment even though Conda is not installed`); } - traceVerbose(`Searching for conda environments using ${conda.command} in resolver`); - const envs = await conda.getEnvList(); - const matchEnv = getEnvMatcher(env); + const envs = (await conda?.getEnvList()) ?? []; + const matchEnv = getEnvMatcher(executablePath); for (const { name, prefix } of envs) { const executable = await getInterpreterPathFromDir(prefix); if (executable && matchEnv(executable)) { @@ -98,11 +99,14 @@ async function resolveCondaEnv(env: string): Promise return info; } } - traceError(`${env} identified as a Conda environment but is not returned via '${conda.command} info' command`); - return undefined; + traceError( + `${executablePath} identified as a Conda environment but is not returned via '${conda?.command} info' command`, + ); + // Environment could still be valid, resolve as a simple env. + return resolveSimpleEnv(executablePath, PythonEnvKind.Conda); } -async function resolvePyenvEnv(executablePath: string): Promise { +async function resolvePyenvEnv(executablePath: string): Promise { const location = getEnvironmentDirFromPath(executablePath); const name = path.basename(location); @@ -123,7 +127,7 @@ async function resolvePyenvEnv(executablePath: string): Promise { +async function resolveWindowsStoreEnv(executablePath: string): Promise { return buildEnvInfo({ kind: PythonEnvKind.WindowsStore, executable: executablePath, diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts index 811053c4b5d4..8ad64d252a85 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts @@ -257,8 +257,8 @@ suite('Python envs locator - Environments Resolver', () => { stubShellExec.restore(); }); - test('Calls into parent locator to get resolved environment, then calls environnment service to resolve environment further and return it', async () => { - const resolvedEnvReturnedByResolver = createExpectedResolvedEnvInfo( + test('Calls into basic resolver to get environment info, then calls environnment service to resolve environment further and return it', async () => { + const resolvedEnvReturnedByBasicResolver = createExpectedResolvedEnvInfo( path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), PythonEnvKind.Venv, undefined, @@ -270,10 +270,10 @@ suite('Python envs locator - Environments Resolver', () => { const expected = await resolver.resolveEnv(path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe')); - assertEnvEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByResolver)); + assertEnvEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByBasicResolver)); }); - test('If the parent locator resolves environment, but running interpreter info throws error, return undefined', async () => { + test('If running interpreter info throws error, return undefined', async () => { stubShellExec.returns( new Promise>((_resolve, reject) => { reject(); @@ -303,17 +303,5 @@ suite('Python envs locator - Environments Resolver', () => { assert.deepEqual(expected, undefined); }); - - test("If the parent locator isn't able to resolve environment, return undefined", async () => { - sinon.stub(ExternalDep, 'exec').callsFake(async (command: string) => { - throw new Error(`Conda binary command ${command} is missing or is not executable`); - }); - const parentLocator = new SimpleLocator([]); - const resolver = new PythonEnvsResolver(parentLocator, envInfoService); - - const expected = await resolver.resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); - - assert.deepEqual(expected, undefined); - }); }); }); diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index a3c9f9670159..76f90a8ec990 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import * as sinon from 'sinon'; import { Uri } from 'vscode'; -import { assert } from 'chai'; import * as externalDependencies from '../../../../../client/pythonEnvironments/common/externalDependencies'; import * as platformApis from '../../../../../client/common/utils/platform'; import { @@ -168,6 +167,31 @@ suite('Resolver Utils', () => { }); return info; } + function createSimpleEnvInfo( + interpreterPath: string, + kind: PythonEnvKind, + version: PythonVersion = UNKNOWN_PYTHON_VERSION, + name = '', + location = '', + ): PythonEnvInfo { + return { + name, + location, + kind, + executable: { + filename: interpreterPath, + sysPrefix: '', + ctime: -1, + mtime: -1, + }, + display: undefined, + version, + arch: Architecture.Unknown, + distro: { org: '' }, + searchLocation: undefined, + source: [PythonEnvSource.Other], + }; + } setup(() => { sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); @@ -208,13 +232,22 @@ suite('Resolver Utils', () => { ); }); - test('resolveEnv: No conda binary found', async () => { + test('resolveEnv: If no conda binary found, resolve as a simple environment', async () => { sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Windows); sinon.stub(externalDependencies, 'exec').callsFake(async (command: string) => { throw new Error(`${command} is missing or is not executable`); }); const actual = await resolveEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe')); - assert.equal(actual, undefined); + assertEnvEqual( + actual, + createSimpleEnvInfo( + path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe'), + PythonEnvKind.Conda, + undefined, + 'conda1', + path.join(TEST_LAYOUT_ROOT, 'conda1'), + ), + ); }); }); From 1003b8241c38ee22cd36704b2736e1241d88fc48 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 17:30:01 -0700 Subject: [PATCH 19/24] Remove resolveEnvFromIterator helper --- .../pythonEnvironments/base/locatorUtils.ts | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/src/client/pythonEnvironments/base/locatorUtils.ts b/src/client/pythonEnvironments/base/locatorUtils.ts index 6f30846700b1..6b05dc1a975a 100644 --- a/src/client/pythonEnvironments/base/locatorUtils.ts +++ b/src/client/pythonEnvironments/base/locatorUtils.ts @@ -165,41 +165,3 @@ export async function* iterAndUpdateEnvs( notify(null); } } - -/** - * Naively implement `ILocator.resolveEnv()` by searching through an iterator. - */ -export async function resolveEnvFromIterator( - env: string | Partial, - iterator: IPythonEnvsIterator, -): Promise { - let resolved: PythonEnvInfo | undefined; - - const matchEnv = getEnvMatcher(env); - - let listener: IDisposable | undefined; - const done = createDeferred(); - if (iterator.onUpdated !== undefined) { - listener = iterator.onUpdated((event: PythonEnvUpdatedEvent | null) => { - if (event === null) { - done.resolve(); - } else if (!event.update || matchEnv(event.update)) { - resolved = event.update; - } - }); - } else { - done.resolve(); - } - for await (const iterated of iterator) { - if (matchEnv(iterated)) { - resolved = iterated; - } - } - await done.promise; - - if (listener !== undefined) { - listener.dispose(); - } - - return resolved; -} From 0f09634d3e4b0bab8c531ff606cd79fbe367df57 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 17:46:02 -0700 Subject: [PATCH 20/24] Fix compilation errors --- src/client/pythonEnvironments/base/info/env.ts | 5 ++--- src/client/pythonEnvironments/base/locatorUtils.ts | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index aa91daba2146..7ba87ff11b18 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -269,14 +269,13 @@ export async function getMaxDerivedEnvInfo(minimal: PythonEnvInfo): Promise): (env: string | PythonEnvInfo) => boolean { +export function getEnvMatcher(query: string): (env: string) => boolean { const executable = getEnvExecutable(query); if (executable === '') { // We could throw an exception error, but skipping it is fine. return () => false; } - function matchEnv(candidate: string | PythonEnvInfo): boolean { - const candidateExecutable = typeof candidate === 'string' ? candidate : candidate.executable.filename; + function matchEnv(candidateExecutable: string): boolean { return arePathsSame(executable, candidateExecutable); } return matchEnv; diff --git a/src/client/pythonEnvironments/base/locatorUtils.ts b/src/client/pythonEnvironments/base/locatorUtils.ts index 6b05dc1a975a..a3819482a114 100644 --- a/src/client/pythonEnvironments/base/locatorUtils.ts +++ b/src/client/pythonEnvironments/base/locatorUtils.ts @@ -5,9 +5,8 @@ import { Uri } from 'vscode'; import { traceVerbose } from '../../common/logger'; import { createDeferred } from '../../common/utils/async'; import { getURIFilter } from '../../common/utils/misc'; -import { IDisposable } from '../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from './info'; -import { getEnvMatcher, getMaxDerivedEnvInfo } from './info/env'; +import { getMaxDerivedEnvInfo } from './info/env'; import { IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery } from './locator'; /** From 90903e5f2df03fb6dd78f81345fdec80a9fa3756 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 17:50:47 -0700 Subject: [PATCH 21/24] Remove unecessary if condition --- .../base/locators/composite/resolverUtils.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index d81d89e81782..65fcfffeaded 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -25,21 +25,19 @@ import { getPythonVersionFromPath as parsePythonVersionFromPath } from '../../in */ export async function resolveEnv(executablePath: string): Promise { const resolved = await doResolveEnv(executablePath); - if (resolved) { - const folders = getWorkspaceFolders(); - const isRootedEnv = folders.some((f) => isParentPath(executablePath, f)); - if (isRootedEnv) { - // For environments inside roots, we need to set search location so they can be queried accordingly. - // Search location particularly for virtual environments is intended as the directory in which the - // environment was found in. - // For eg.the default search location for an env containing 'bin' or 'Scripts' directory is: - // - // searchLocation <--- Default search location directory - // |__ env - // |__ bin or Scripts - // |__ python <--- executable - resolved.searchLocation = Uri.file(path.dirname(resolved.location)); - } + const folders = getWorkspaceFolders(); + const isRootedEnv = folders.some((f) => isParentPath(executablePath, f)); + if (isRootedEnv) { + // For environments inside roots, we need to set search location so they can be queried accordingly. + // Search location particularly for virtual environments is intended as the directory in which the + // environment was found in. + // For eg.the default search location for an env containing 'bin' or 'Scripts' directory is: + // + // searchLocation <--- Default search location directory + // |__ env + // |__ bin or Scripts + // |__ python <--- executable + resolved.searchLocation = Uri.file(path.dirname(resolved.location)); } return resolved; } From ba33dd29519cb835674564abc33fa7ec186007cd Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 7 Jul 2021 23:50:16 -0700 Subject: [PATCH 22/24] Abstract out kind-specific resolver functions in resolveEnv --- .../base/locators/composite/resolverUtils.ts | 35 ++++++++++--------- .../common/environmentIdentifier.ts | 4 +-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 65fcfffeaded..859ccdc416c1 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -18,13 +18,28 @@ import { parsePyenvVersion } from '../../../discovery/locators/services/pyenvLoc import { Architecture } from '../../../../common/utils/platform'; import { getPythonVersionFromPath as parsePythonVersionFromPath } from '../../info/pythonVersion'; +function getResolvers(): Map Promise> { + const resolvers = new Map Promise>(); + const defaultResolver = (k: PythonEnvKind) => (e: string) => resolveSimpleEnv(e, k); + Object.values(PythonEnvKind).forEach((k) => { + resolvers.set(k, defaultResolver(k)); + }); + resolvers.set(PythonEnvKind.Conda, resolveCondaEnv); + resolvers.set(PythonEnvKind.WindowsStore, resolveWindowsStoreEnv); + resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); + return resolvers; +} + /** * Find as much info about the given Python environment as possible without running the * Python executable and returns it. Notice `undefined` is never returned, so environment * returned could still be invalid. */ export async function resolveEnv(executablePath: string): Promise { - const resolved = await doResolveEnv(executablePath); + const kind = await identifyEnvironment(executablePath); + const resolvers = getResolvers(); + const resolverForKind = resolvers.get(kind)!; + const resolvedEnv = await resolverForKind(executablePath); const folders = getWorkspaceFolders(); const isRootedEnv = folders.some((f) => isParentPath(executablePath, f)); if (isRootedEnv) { @@ -37,23 +52,9 @@ export async function resolveEnv(executablePath: string): Promise // |__ env // |__ bin or Scripts // |__ python <--- executable - resolved.searchLocation = Uri.file(path.dirname(resolved.location)); - } - return resolved; -} - -async function doResolveEnv(executablePath: string): Promise { - const kind = await identifyEnvironment(executablePath); - switch (kind) { - case PythonEnvKind.Conda: - return resolveCondaEnv(executablePath); - case PythonEnvKind.Pyenv: - return resolvePyenvEnv(executablePath); - case PythonEnvKind.WindowsStore: - return resolveWindowsStoreEnv(executablePath); - default: - return resolveSimpleEnv(executablePath, kind); + resolvedEnv.searchLocation = Uri.file(path.dirname(resolvedEnv.location)); } + return resolvedEnv; } async function resolveSimpleEnv(executablePath: string, kind: PythonEnvKind): Promise { diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 3bbad18414b2..03756ee09c7e 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -18,8 +18,8 @@ function getIdentifiers(): Map Promise const notImplemented = () => Promise.resolve(false); const defaultTrue = () => Promise.resolve(true); const identifier: Map Promise> = new Map(); - Object.keys(PythonEnvKind).forEach((k: string) => { - identifier.set(k as PythonEnvKind, notImplemented); + Object.values(PythonEnvKind).forEach((k) => { + identifier.set(k, notImplemented); }); identifier.set(PythonEnvKind.Conda, isCondaEnvironment); From ff702e23b67f6e1ccb4247163f58569ce84ecf3c Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 8 Jul 2021 00:09:27 -0700 Subject: [PATCH 23/24] Change OS to windows --- .../locators/composite/environmentsResolver.unit.test.ts | 2 +- .../base/locators/composite/resolverUtils.unit.test.ts | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts index 8ad64d252a85..69913e6fc753 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts @@ -239,7 +239,7 @@ suite('Python envs locator - Environments Resolver', () => { }; } setup(() => { - sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Linux); + sinon.stub(platformApis, 'getOSType').callsFake(() => platformApis.OSType.Windows); stubShellExec = ImportMock.mockFunction( ExternalDep, 'shellExecute', diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 76f90a8ec990..1e2f6e1e3e8f 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -197,10 +197,6 @@ suite('Resolver Utils', () => { sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([]); }); - suiteTeardown(() => { - sinon.restore(); - }); - teardown(() => { sinon.restore(); }); @@ -253,11 +249,11 @@ suite('Resolver Utils', () => { suite('Simple envs', () => { const testVirtualHomeDir = path.join(TEST_LAYOUT_ROOT, 'virtualhome'); - suiteSetup(() => { + setup(() => { sinon.stub(externalDependencies, 'getWorkspaceFolders').returns([testVirtualHomeDir]); }); - suiteTeardown(() => { + teardown(() => { sinon.restore(); }); From f6816b8f7ae635e17c66c37d2416fd94def0eaef Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 8 Jul 2021 16:39:44 -0700 Subject: [PATCH 24/24] Remove doResolveEnv implementation from workspace locator --- .../lowLevel/workspaceVirtualEnvLocator.ts | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts index 54a37b115835..53d7acaac319 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { uniq } from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { traceError, traceVerbose } from '../../../../common/logger'; @@ -12,7 +11,7 @@ import { getPythonVersionFromPath, looksLikeBasicVirtualPython, } from '../../../common/commonUtils'; -import { getFileInfo, isParentPath, pathExists } from '../../../common/externalDependencies'; +import { getFileInfo, pathExists } from '../../../common/externalDependencies'; import { isPipenvEnvironment } from '../../../discovery/locators/services/pipEnvHelper'; import { isVenvEnvironment, @@ -147,21 +146,4 @@ export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator { return iterator(this.root); } - - // eslint-disable-next-line class-methods-use-this - protected async doResolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - const source = typeof env === 'string' ? [PythonEnvSource.Other] : uniq([PythonEnvSource.Other, ...env.source]); - if (isParentPath(executablePath, this.root) && (await pathExists(executablePath))) { - // We should extract the kind here to avoid doing is*Environment() - // check multiple times. Those checks are file system heavy and - // we can use the kind to determine this anyway. - const kind = await getVirtualEnvKind(executablePath); - if (kind === PythonEnvKind.Unknown) { - return undefined; - } - return buildSimpleVirtualEnvInfo(executablePath, kind, source); - } - return undefined; - } }