From 218dd8de3ac990a3587d2cc157e6f44d18cd9dd7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Sep 2020 19:31:08 -0600 Subject: [PATCH] Add PythonEnvInfo-related helpers. (#14051) This PR adds some basic helpers that we use in a subsequent PR. The following small drive-by changes are also included: * drop PythonEnvInfo.id property * make some internal helpers public --- src/client/common/utils/misc.ts | 47 ++ .../pythonEnvironments/base/info/env.ts | 139 +++++- .../pythonEnvironments/base/info/index.ts | 13 +- .../base/info/pythonVersion.ts | 6 +- src/client/pythonEnvironments/base/locator.ts | 37 +- .../pythonEnvironments/base/locatorUtils.ts | 110 ++++ .../locators/composite/environmentsReducer.ts | 20 +- .../composite/environmentsResolver.ts | 14 +- .../discovery/locators/index.ts | 26 +- src/client/pythonEnvironments/legacyIOC.ts | 91 +--- src/test/pythonEnvironments/base/common.ts | 59 +-- .../base/locatorUtils.unit.test.ts | 472 ++++++++++++++++++ .../base/locators.unit.test.ts | 52 +- .../environmentsReducer.unit.test.ts | 90 ++-- .../environmentsResolver.unit.test.ts | 38 +- .../discovery/locators/index.unit.test.ts | 164 +++--- 16 files changed, 1042 insertions(+), 336 deletions(-) create mode 100644 src/client/pythonEnvironments/base/locatorUtils.ts create mode 100644 src/test/pythonEnvironments/base/locatorUtils.unit.test.ts diff --git a/src/client/common/utils/misc.ts b/src/client/common/utils/misc.ts index 8c6783b78c9f..0a1186b6da0a 100644 --- a/src/client/common/utils/misc.ts +++ b/src/client/common/utils/misc.ts @@ -130,6 +130,53 @@ export function isUri(resource?: Uri | any): resource is Uri { return typeof uri.path === 'string' && typeof uri.scheme === 'string'; } +/** + * Create a filter func that determine if the given URI and candidate match. + * + * The scheme must match, as well as path. + * + * @param checkParent - if `true`, match if the candidate is rooted under `uri` + * @param checkChild - if `true`, match if `uri` is rooted under the candidate + * @param checkExact - if `true`, match if the candidate matches `uri` exactly + */ +export function getURIFilter( + uri: Uri, + opts: { + checkParent?: boolean; + checkChild?: boolean; + checkExact?: boolean; + } = { checkExact: true } +): (u: Uri) => boolean { + let uriPath = uri.path; + while (uri.path.endsWith('/')) { + uriPath = uriPath.slice(0, -1); + } + const uriRoot = `${uriPath}/`; + function filter(candidate: Uri): boolean { + if (candidate.scheme !== uri.scheme) { + return false; + } + let candidatePath = candidate.path; + while (candidate.path.endsWith('/')) { + candidatePath = candidatePath.slice(0, -1); + } + if (opts.checkExact && candidatePath === uriPath) { + return true; + } + if (opts.checkParent && candidatePath.startsWith(uriRoot)) { + return true; + } + if (opts.checkChild) { + const candidateRoot = `{candidatePath}/`; + if (uriPath.startsWith(candidateRoot)) { + return true; + } + } + return false; + } + return filter; +} + export function isNotebookCell(documentOrUri: TextDocument | Uri): boolean { const uri = isUri(documentOrUri) ? documentOrUri : documentOrUri.uri; return uri.scheme.includes(NotebookCellScheme); diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index 5d88a27c0710..bb72186ca7a5 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -3,14 +3,123 @@ import { cloneDeep } from 'lodash'; import * as path from 'path'; +import { Architecture } from '../../../common/utils/platform'; +import { arePathsSame } from '../../common/externalDependencies'; +import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; + import { FileInfo, PythonDistroInfo, - PythonEnvInfo, PythonEnvKind, PythonVersion, + PythonEnvInfo, + PythonEnvKind, + PythonReleaseLevel, + PythonVersion, } from '.'; -import { Architecture } from '../../../common/utils/platform'; -import { arePathsSame } from '../../common/externalDependencies'; -import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; + +/** + * Create a new info object with all values empty. + * + * @param init - if provided, these values are applied to the new object + */ +export function buildEnvInfo(init?: { + kind?: PythonEnvKind; + executable?: string; + location?: string; + version?: PythonVersion; +}): PythonEnvInfo { + const env = { + kind: PythonEnvKind.Unknown, + executable: { + filename: '', + sysPrefix: '', + ctime: -1, + mtime: -1, + }, + name: '', + location: '', + version: { + major: -1, + minor: -1, + micro: -1, + release: { + level: PythonReleaseLevel.Final, + serial: 0, + }, + }, + arch: Architecture.Unknown, + distro: { + org: '', + }, + }; + if (init !== undefined) { + updateEnv(env, init); + } + return env; +} + +/** + * Return a deep copy of the given env info. + * + * @param updates - if provided, these values are applied to the copy + */ +export function copyEnvInfo( + env: PythonEnvInfo, + updates?: { + kind?: PythonEnvKind, + }, +): PythonEnvInfo { + // We don't care whether or not extra/hidden properties + // get preserved, so we do the easy thing here. + const copied = cloneDeep(env); + if (updates !== undefined) { + updateEnv(copied, updates); + } + return copied; +} + +function updateEnv(env: PythonEnvInfo, updates: { + kind?: PythonEnvKind; + executable?: string; + location?: string; + version?: PythonVersion; +}): void { + if (updates.kind !== undefined) { + env.kind = updates.kind; + } + if (updates.executable !== undefined) { + env.executable.filename = updates.executable; + } + if (updates.location !== undefined) { + env.location = updates.location; + } + if (updates.version !== undefined) { + env.version = updates.version; + } +} + +/** + * For the given data, build a normalized partial info object. + * + * If insufficient data is provided to generate a minimal object, such + * that it is not identifiable, then `undefined` is returned. + */ +export function getMinimalPartialInfo(env: string | Partial): Partial | undefined { + if (typeof env === 'string') { + if (env === '') { + return undefined; + } + return { + executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 }, + }; + } + if (env.executable === undefined) { + return undefined; + } + if (env.executable.filename === '') { + return undefined; + } + return env; +} /** * Checks if two environments are same. @@ -24,14 +133,20 @@ import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; * to be same environment. This later case is needed for comparing windows store python, * where multiple versions of python executables are all put in the same directory. */ -export function areSameEnvironment( - left: string | PythonEnvInfo, - right: string | PythonEnvInfo, +export function areSameEnv( + left: string | Partial, + right: string | Partial, allowPartialMatch?: boolean, -): boolean { - const leftFilename = typeof left === 'string' ? left : left.executable.filename; - const rightFilename = typeof right === 'string' ? right : right.executable.filename; +): boolean | undefined { + const leftInfo = getMinimalPartialInfo(left); + const rightInfo = getMinimalPartialInfo(right); + if (leftInfo === undefined || rightInfo === undefined) { + return undefined; + } + const leftFilename = leftInfo.executable!.filename; + const rightFilename = rightInfo.executable!.filename; + // For now we assume that matching executable means they are the same. if (arePathsSame(leftFilename, rightFilename)) { return true; } @@ -72,11 +187,11 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number { infoLevel += 5; // W2 } - if (version.release.level) { + if (version.release?.level) { infoLevel += 3; // W1 } - if (version.release.serial || version.sysVersion) { + if (version.release?.serial || version.sysVersion) { infoLevel += 1; // W0 } diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 5298a3d73f9e..4418860a12e5 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -28,6 +28,11 @@ export enum PythonEnvKind { OtherVirtual = 'virt-other' } +/** + * A (system-global) unique ID for a single Python environment. + */ +export type PythonEnvID = string; + /** * Information about a file. */ @@ -44,11 +49,6 @@ export type PythonExecutableInfo = FileInfo & { sysPrefix: string; }; -/** - * A (system-global) unique ID for a single Python environment. - */ -export type PythonEnvID = string; - /** * The most fundamental information about a Python environment. * @@ -63,7 +63,6 @@ export type PythonEnvID = string; * @prop location - the env's location (on disk), if relevant */ export type PythonEnvBaseInfo = { - id: PythonEnvID; kind: PythonEnvKind; executable: PythonExecutableInfo; // One of (name, location) must be non-empty. @@ -99,7 +98,7 @@ export type PythonVersionRelease = { * @prop sysVersion - the raw text from `sys.version` */ export type PythonVersion = BasicVersionInfo & { - release: PythonVersionRelease; + release?: PythonVersionRelease; sysVersion?: string; }; diff --git a/src/client/pythonEnvironments/base/info/pythonVersion.ts b/src/client/pythonEnvironments/base/info/pythonVersion.ts index 40e663c6231e..0e281eb4662c 100644 --- a/src/client/pythonEnvironments/base/info/pythonVersion.ts +++ b/src/client/pythonEnvironments/base/info/pythonVersion.ts @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { PythonReleaseLevel, PythonVersion } from '.'; import { EMPTY_VERSION, parseBasicVersionInfo } from '../../../common/utils/version'; +import { PythonReleaseLevel, PythonVersion } from '.'; + +/** + * Convert the given string into the corresponding Python version object. + */ export function parseVersion(versionStr: string): PythonVersion { const parsed = parseBasicVersionInfo(versionStr); if (!parsed) { diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 03eb206445bd..a1b372fb6988 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -15,17 +15,18 @@ import { * A single update to a previously provided Python env object. */ export type PythonEnvUpdatedEvent = { + /** + * The iteration index of The env info that was previously provided. + */ + index: number; /** * The env info that was previously provided. - * - * If the event comes from `IPythonEnvsIterator.onUpdated` then - * `old` was previously yielded during iteration. */ - old: PythonEnvInfo; + old?: PythonEnvInfo; /** * The env info that replaces the old info. */ - new: PythonEnvInfo; + update: PythonEnvInfo; }; /** @@ -73,23 +74,39 @@ export const NOOP_ITERATOR: IPythonEnvsIterator = iterEmpty(); * This is directly correlated with the `BasicPythonEnvsChangedEvent` * emitted by watchers. * - * @prop kinds - if provided, results should be limited to these env kinds + * @prop kinds - if provided, results should be limited to these env + * kinds; if not provided, the kind of each evnironment + * is not considered when filtering */ export type BasicPythonLocatorQuery = { kinds?: PythonEnvKind[]; }; +/** + * The portion of a query related to env search locations. + */ +export type SearchLocations = { + /** + * The locations under which to look for environments. + */ + roots: Uri[]; + /** + * If true, also look for environments that do not have a search location. + */ + includeNonRooted?: boolean; +}; + /** * The full set of possible info to send to a locator when requesting environments. * * This is directly correlated with the `PythonEnvsChangedEvent` * emitted by watchers. - * - * @prop - searchLocations - if provided, results should be limited to - * within these locations */ export type PythonLocatorQuery = BasicPythonLocatorQuery & { - searchLocations?: Uri[]; + /** + * If provided, results should be limited to within these locations. + */ + searchLocations?: SearchLocations; }; type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; diff --git a/src/client/pythonEnvironments/base/locatorUtils.ts b/src/client/pythonEnvironments/base/locatorUtils.ts new file mode 100644 index 000000000000..b7c2e496809f --- /dev/null +++ b/src/client/pythonEnvironments/base/locatorUtils.ts @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri } from 'vscode'; +import { createDeferred } from '../../common/utils/async'; +import { getURIFilter } from '../../common/utils/misc'; +import { PythonEnvInfo } from './info'; +import { + IPythonEnvsIterator, + PythonEnvUpdatedEvent, + PythonLocatorQuery, +} from './locator'; + +/** + * Create a filter function to match the given query. + */ +export function getQueryFilter(query: PythonLocatorQuery): (env: PythonEnvInfo) => boolean { + const kinds = (query.kinds !== undefined && query.kinds.length > 0) + ? query.kinds + : undefined; + let includeNonRooted = true; + if (query.searchLocations !== undefined) { + if (query.searchLocations.includeNonRooted !== undefined) { + includeNonRooted = query.searchLocations.includeNonRooted; + } else { + // We default to `false`. + includeNonRooted = false; + } + } + const locationFilters = getSearchLocationFilters(query); + function checkKind(env: PythonEnvInfo): boolean { + if (kinds === undefined) { + return true; + } + return kinds.includes(env.kind); + } + function checkSearchLocation(env: PythonEnvInfo): boolean { + if (env.searchLocation === undefined) { + // It is not a "rooted" env. + return includeNonRooted; + } else { + // It is a "rooted" env. + const loc = env.searchLocation; + if (locationFilters !== undefined) { + // Check against the requested roots. (There may be none.) + return locationFilters.some((filter) => filter(loc)); + } + return true; + } + } + return (env) => { + if (!checkKind(env)) { + return false; + } + if (!checkSearchLocation(env)) { + return false; + } + return true; + }; +} + +function getSearchLocationFilters(query: PythonLocatorQuery): ((u: Uri) => boolean)[] | undefined { + if (query.searchLocations === undefined) { + return undefined; + } + if (query.searchLocations.roots.length === 0) { + return []; + } + return query.searchLocations.roots.map((loc) => getURIFilter(loc, { + checkParent: true, + checkExact: true, + })); +} + +/** + * Unroll the given iterator into an array. + * + * This includes applying any received updates. + */ +export async function getEnvs(iterator: IPythonEnvsIterator): Promise { + const envs: PythonEnvInfo[] = []; + + const updatesDone = createDeferred(); + if (iterator.onUpdated === undefined) { + updatesDone.resolve(); + } else { + iterator.onUpdated((event: PythonEnvUpdatedEvent | null) => { + if (event === null) { + updatesDone.resolve(); + return; + } + const oldEnv = envs[event.index]; + if (oldEnv === undefined) { + // XXX log or fail + } else { + envs[event.index] = event.update; + } + }); + } + + let result = await iterator.next(); + while (!result.done) { + envs.push(result.value); + // eslint-disable-next-line no-await-in-loop + result = await iterator.next(); + } + + await updatesDone.promise; + return envs; +} diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts index 617160c05b11..6c3a71444df8 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -6,7 +6,7 @@ import { Event, EventEmitter } from 'vscode'; import { traceVerbose } from '../../../../common/logger'; import { createDeferred } from '../../../../common/utils/async'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; -import { areSameEnvironment } from '../../info/env'; +import { areSameEnv } from '../../info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, } from '../../locator'; @@ -29,13 +29,13 @@ export class PythonEnvsReducer implements ILocator { iterator.onUpdated!((event) => { if (event === null) { waitForUpdatesDeferred.resolve(); - } else if (environment && areSameEnvironment(environment, event.new)) { - environment = event.new; + } else if (environment && areSameEnv(environment, event.update)) { + environment = event.update; } }); let result = await iterator.next(); while (!result.done) { - if (areSameEnvironment(result.value, env)) { + if (areSameEnv(result.value, env)) { environment = result.value; } // eslint-disable-next-line no-await-in-loop @@ -73,13 +73,13 @@ async function* iterEnvsIterator( state.done = true; checkIfFinishedAndNotify(state, didUpdate); } else { - const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old)); - if (oldIndex !== -1) { + if (seen[event.index] !== undefined) { state.pending += 1; - resolveDifferencesInBackground(oldIndex, event.new, state, didUpdate, seen).ignoreErrors(); + resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen) + .ignoreErrors(); } else { // This implies a problem in a downstream locator - traceVerbose(`Expected already iterated env, got ${event.old}`); + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); } } }); @@ -88,7 +88,7 @@ async function* iterEnvsIterator( let result = await iterator.next(); while (!result.done) { const currEnv = result.value; - const oldIndex = seen.findIndex((s) => areSameEnvironment(s, currEnv)); + const oldIndex = seen.findIndex((s) => areSameEnv(s, currEnv)); if (oldIndex !== -1) { state.pending += 1; resolveDifferencesInBackground(oldIndex, currEnv, state, didUpdate, seen).ignoreErrors(); @@ -116,8 +116,8 @@ async function resolveDifferencesInBackground( const oldEnv = seen[oldIndex]; const merged = mergeEnvironments(oldEnv, newEnv); if (!isEqual(oldEnv, merged)) { - didUpdate.fire({ old: oldEnv, new: merged }); seen[oldIndex] = merged; + didUpdate.fire({ index: oldIndex, old: oldEnv, update: merged }); } state.pending -= 1; checkIfFinishedAndNotify(state, didUpdate); diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index 83ea21e726e5..305c9f22824f 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -6,7 +6,6 @@ import { Event, EventEmitter } from 'vscode'; import { traceVerbose } from '../../../../common/logger'; import { IEnvironmentInfoService } from '../../../info/environmentInfoService'; import { PythonEnvInfo } from '../../info'; -import { areSameEnvironment } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, @@ -63,14 +62,14 @@ export class PythonEnvsResolver implements ILocator { state.done = true; checkIfFinishedAndNotify(state, didUpdate); } else { - const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old)); - if (oldIndex !== -1) { - seen[oldIndex] = event.new; + if (seen[event.index] !== undefined) { + seen[event.index] = event.update; state.pending += 1; - this.resolveInBackground(oldIndex, state, didUpdate, seen).ignoreErrors(); + this.resolveInBackground(event.index, state, didUpdate, seen) + .ignoreErrors(); } else { // This implies a problem in a downstream locator - traceVerbose(`Expected already iterated env in resolver, got ${event.old}`); + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); } } }); @@ -103,8 +102,9 @@ export class PythonEnvsResolver implements ILocator { ); if (interpreterInfo) { const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]); - didUpdate.fire({ old: seen[envIndex], new: resolvedEnv }); + const old = seen[envIndex]; seen[envIndex] = resolvedEnv; + didUpdate.fire({ old, index: envIndex, update: resolvedEnv }); } state.pending -= 1; checkIfFinishedAndNotify(state, didUpdate); diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts index 67f38e0fe769..49bec156a39b 100644 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ b/src/client/pythonEnvironments/discovery/locators/index.ts @@ -9,6 +9,7 @@ import { traceDecorators } from '../../../common/logger'; import { IPlatformService } from '../../../common/platform/types'; import { IDisposableRegistry } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; +import { getURIFilter } from '../../../common/utils/misc'; import { OSType } from '../../../common/utils/platform'; import { CONDA_ENV_FILE_SERVICE, @@ -97,10 +98,18 @@ export class WorkspaceLocators extends Locator { } public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + if (query?.searchLocations === null) { + // Workspace envs all have searchLocation, so there's nothing to do. + return NOOP_ITERATOR; + } const iterators = Object.keys(this.locators).map((key) => { - if (query?.searchLocations) { + if (query?.searchLocations !== undefined) { const root = this.roots[key]; - if (!matchURI(root, ...query.searchLocations)) { + // Match any related search location. + const filter = getURIFilter(root, { checkParent: true, checkChild: true, checkExact: true }); + // Ignore any requests for global envs. + if (!query.searchLocations.roots.some(filter)) { + // This workspace folder did not match the query, so skip it! return NOOP_ITERATOR; } } @@ -168,19 +177,6 @@ export class WorkspaceLocators extends Locator { } } -/** - * Determine if the given URI matches one of the candidates. - * - * The scheme must match, as well as path. The path must match exactly - * or the URI must be a parent of one of the candidates. - */ -function matchURI(uri: Uri, ...candidates: Uri[]): boolean { - const uriPath = uri.path.endsWith('/') ? uri.path : '{uri.path}/'; - const matchedUri = candidates.find((candidate) => (candidate.scheme === uri.scheme) - && (candidate.path === uri.path || candidate.path.startsWith(uriPath))); - return matchedUri !== undefined; -} - // The parts of IComponentAdapter used here. interface IComponent { hasInterpreters: Promise; diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index fbeb6cc097a3..d6c199d9e301 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -3,8 +3,6 @@ import { injectable } from 'inversify'; import * as vscode from 'vscode'; -import { createDeferred } from '../common/utils/async'; -import { Architecture } from '../common/utils/platform'; import { getVersionString, parseVersion } from '../common/utils/version'; import { CONDA_ENV_FILE_SERVICE, @@ -29,7 +27,9 @@ import { import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../interpreter/locators/types'; import { IServiceContainer, IServiceManager } from '../ioc/types'; import { PythonEnvInfo, PythonEnvKind, PythonReleaseLevel } from './base/info'; +import { buildEnvInfo } from './base/info/env'; import { ILocator, PythonLocatorQuery } from './base/locator'; +import { getEnvs } from './base/locatorUtils'; import { initializeExternalDependencies } from './common/externalDependencies'; import { PythonInterpreterLocatorService } from './discovery/locators'; import { InterpreterLocatorHelper } from './discovery/locators/helpers'; @@ -106,13 +106,19 @@ function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { if (version !== undefined) { const { release, sysVersion } = version; - const { level, serial } = release; - const releaseStr = level === PythonReleaseLevel.Final - ? 'final' - : `${level}${serial}`; - const versionStr = `${getVersionString(version)}-${releaseStr}`; - env.version = parseVersion(versionStr); - env.sysVersion = sysVersion; + if (release === undefined) { + const versionStr = `${getVersionString(version)}-final`; + env.version = parseVersion(versionStr); + env.sysVersion = ''; + } else { + const { level, serial } = release; + const releaseStr = level === PythonReleaseLevel.Final + ? 'final' + : `${level}${serial}`; + const versionStr = `${getVersionString(version)}-${releaseStr}`; + env.version = parseVersion(versionStr); + env.sysVersion = sysVersion; + } } if (distro !== undefined && distro.org !== '') { @@ -124,34 +130,6 @@ function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { return env; } -function buildEmptyEnvInfo(): PythonEnvInfo { - return { - id: '', - kind: PythonEnvKind.Unknown, - executable: { - filename: '', - sysPrefix: '', - ctime: -1, - mtime: -1, - }, - name: '', - location: '', - version: { - major: -1, - minor: -1, - micro: -1, - release: { - level: PythonReleaseLevel.Final, - serial: 0, - }, - }, - arch: Architecture.Unknown, - distro: { - org: '', - }, - }; -} - interface IPythonEnvironments extends ILocator {} @injectable() @@ -201,8 +179,7 @@ class ComponentAdapter implements IComponentAdapter { if (!this.enabled) { return undefined; } - const info = buildEmptyEnvInfo(); - info.executable.filename = pythonPath; + const info = buildEnvInfo({ executable: pythonPath }); if (resource !== undefined) { const wsFolder = vscode.workspace.getWorkspaceFolder(resource); if (wsFolder !== undefined) { @@ -290,43 +267,13 @@ class ComponentAdapter implements IComponentAdapter { if (resource !== undefined) { const wsFolder = vscode.workspace.getWorkspaceFolder(resource); if (wsFolder !== undefined) { - query.searchLocations = [wsFolder.uri]; + query.searchLocations = { roots: [wsFolder.uri] }; } } - const deferred = createDeferred(); - const envs: PythonEnvironment[] = []; - const executableToLegacy: Record = {}; const iterator = this.api.iterEnvs(query); - - if (iterator.onUpdated !== undefined) { - iterator.onUpdated((event) => { - if (event === null) { - deferred.resolve(envs); - } else { - // Replace the old one. - const old = executableToLegacy[event.old.executable.filename]; - if (old !== undefined) { - const index = envs.indexOf(old); - if (index !== -1) { - envs[index] = convertEnvInfo(event.new); - } - } - } - }); - } else { - deferred.resolve(envs); - } - - let res = await iterator.next(); - while (!res.done) { - const env = convertEnvInfo(res.value); - envs.push(env); - executableToLegacy[env.path] = env; - res = await iterator.next(); // eslint-disable-line no-await-in-loop - } - - return deferred.promise; + const envs = await getEnvs(iterator); + return envs.map(convertEnvInfo); } } diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index d1d3b21cc7cc..62077e46aa20 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as path from 'path'; import { Event } from 'vscode'; import { createDeferred, flattenIterator, iterable, mapToIterator, @@ -10,56 +11,38 @@ import { PythonEnvInfo, PythonEnvKind, } from '../../../client/pythonEnvironments/base/info'; +import { buildEnvInfo } from '../../../client/pythonEnvironments/base/info/env'; import { parseVersion } from '../../../client/pythonEnvironments/base/info/pythonVersion'; import { IPythonEnvsIterator, Locator, PythonEnvUpdatedEvent, PythonLocatorQuery, } from '../../../client/pythonEnvironments/base/locator'; import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher'; -export function createEnv( - name: string, +export function createLocatedEnv( + locationStr: string, versionStr: string, - kind?: PythonEnvKind, - executable?: string, - idStr?: string + kind = PythonEnvKind.Unknown, + execStr = 'python', ): PythonEnvInfo { - if (kind === undefined) { - kind = PythonEnvKind.Unknown; - } - if (executable === undefined || executable === '') { - executable = 'python'; - } - const id = idStr ? idStr : `${kind}-${name}`; + const location = locationStr === '' ? '' : path.normalize(locationStr); + const normalizedExecutable = path.normalize(execStr); + const executable = location === '' || path.isAbsolute(normalizedExecutable) + ? normalizedExecutable + : path.join(location, 'bin', normalizedExecutable); const version = parseVersion(versionStr); - return { - id, - kind, - version, - name, - location: '', - arch: Architecture.x86, - executable: { - filename: executable, - sysPrefix: '', - mtime: -1, - ctime: -1 - }, - distro: { org: '' } - }; + const env = buildEnvInfo({ kind, executable, location, version }); + env.arch = Architecture.x86; + return env; } -export function createLocatedEnv( - location: string, +export function createNamedEnv( + name: string, versionStr: string, - kind = PythonEnvKind.Unknown, - executable = 'python', - idStr?: string + kind?: PythonEnvKind, + execStr = 'python', ): PythonEnvInfo { - if (!idStr) { - idStr = `${kind}-${location}`; - } - const env = createEnv('', versionStr, kind, executable, idStr); - env.location = location; + const env = createLocatedEnv('', versionStr, kind, execStr); + env.name = name; return env; } @@ -125,7 +108,7 @@ export class SimpleLocator extends Locator { return iterator; } public async resolveEnv(env: string | PythonEnvInfo): Promise { - const envInfo: PythonEnvInfo = typeof env === 'string' ? createEnv('', '', undefined, env) : env; + const envInfo: PythonEnvInfo = typeof env === 'string' ? createLocatedEnv('', '', undefined, env) : env; if (this.callbacks?.resolve === undefined) { return envInfo; } else if (this.callbacks?.resolve === null) { diff --git a/src/test/pythonEnvironments/base/locatorUtils.unit.test.ts b/src/test/pythonEnvironments/base/locatorUtils.unit.test.ts new file mode 100644 index 000000000000..fe297360e7a8 --- /dev/null +++ b/src/test/pythonEnvironments/base/locatorUtils.unit.test.ts @@ -0,0 +1,472 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import * as path from 'path'; +import { EventEmitter, Uri } from 'vscode'; +import { getValues as getEnumValues } from '../../../client/common/utils/enum'; +import { PythonEnvInfo, PythonEnvKind } from '../../../client/pythonEnvironments/base/info'; +import { copyEnvInfo } from '../../../client/pythonEnvironments/base/info/env'; +import { + IPythonEnvsIterator, + PythonEnvUpdatedEvent, + PythonLocatorQuery, +} from '../../../client/pythonEnvironments/base/locator'; +import { getEnvs, getQueryFilter } from '../../../client/pythonEnvironments/base/locatorUtils'; +import { + createLocatedEnv, + createNamedEnv, +} from './common'; + +const homeDir = path.normalize('/home/me'); +const workspaceRoot = Uri.file('workspace-root'); +const doesNotExist = Uri.file(path.normalize('does-not-exist')); + +function setSearchLocation(env: PythonEnvInfo, location?: string): void { + const locationStr = location === undefined + ? path.dirname(env.location) + : path.normalize(location); + env.searchLocation = Uri.file(locationStr); +} + +const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.System, '/usr/bin/python3.8'); +const env2 = createNamedEnv('env2', '3.8.1rc2', PythonEnvKind.Pyenv, '/pyenv/3.8.1rc2/bin/python'); +const env3 = createNamedEnv('env3', '3.9.1b2', PythonEnvKind.Unknown, 'python3.9'); +const env4 = createNamedEnv('env4', '2.7.11', PythonEnvKind.Pyenv, '/pyenv/2.7.11/bin/python'); +const env5 = createNamedEnv('env5', '2.7', PythonEnvKind.System, 'python2'); +const env6 = createNamedEnv('env6', '3.7.4', PythonEnvKind.Conda, 'python'); +const plainEnvs = [env1, env2, env3, env4, env5, env6]; + +const envL1 = createLocatedEnv('/.venvs/envL1', '3.9.0', PythonEnvKind.Venv); +const envL2 = createLocatedEnv('/conda/envs/envL2', '3.8.3', PythonEnvKind.Conda); +const locatedEnvs = [envL1, envL2]; + +const envS1 = createNamedEnv('env S1', '3.9', PythonEnvKind.OtherVirtual, `${homeDir}/some-dir/bin/python`); +setSearchLocation(envS1, homeDir); +const envS2 = createNamedEnv('env S2', '3.9', PythonEnvKind.OtherVirtual, `${homeDir}/some-dir2/bin/python`); +setSearchLocation(envS2, homeDir); +const envS3 = createNamedEnv('env S2', '3.9', PythonEnvKind.OtherVirtual, `${workspaceRoot.fsPath}/p/python`); +envS3.searchLocation = workspaceRoot; +const rootedEnvs = [envS1, envS2, envS3]; + +const envSL1 = createLocatedEnv(`${homeDir}/.venvs/envSL1`, '3.9.0', PythonEnvKind.Venv); +setSearchLocation(envSL1); +const envSL2 = createLocatedEnv(`${workspaceRoot.fsPath}/.venv`, '3.8.2', PythonEnvKind.Pipenv); +setSearchLocation(envSL2); +const envSL3 = createLocatedEnv(`${homeDir}/.conda-envs/envSL3`, '3.8.2', PythonEnvKind.Conda); +setSearchLocation(envSL3); +const envSL4 = createLocatedEnv('/opt/python3.10', '3.10.0a1', PythonEnvKind.Custom); +setSearchLocation(envSL4); +const envSL5 = createLocatedEnv(`${homeDir}/.venvs/envSL5`, '3.9.0', PythonEnvKind.Venv); +setSearchLocation(envSL5); +const rootedLocatedEnvs = [envSL1, envSL2, envSL3, envSL4, envSL5]; + +const envs = [ + ...plainEnvs, + ...locatedEnvs, + ...rootedEnvs, + ...rootedLocatedEnvs, +]; + +suite('Python envs locator utils - getQueryFilter', () => { + suite('empty query', () => { + const queries: PythonLocatorQuery[] = [ + {}, + { kinds: [] }, + // Any "defined" value for searchLocations causes filtering... + ]; + queries.forEach((query) => { + test(`all envs kept (query ${query})`, () => { + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, envs); + }); + }); + }); + + suite('kinds', () => { + test('match none', () => { + const query: PythonLocatorQuery = { kinds: [PythonEnvKind.MacDefault] }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, []); + }); + + ([ + [PythonEnvKind.Unknown, [env3]], + [PythonEnvKind.System, [env1, env5]], + [PythonEnvKind.WindowsStore, []], + [PythonEnvKind.Pyenv, [env2, env4]], + [PythonEnvKind.Venv, [envL1, envSL1, envSL5]], + [PythonEnvKind.Conda, [env6, envL2, envSL3]], + ] as [PythonEnvKind, PythonEnvInfo[]][]).forEach(([kind, expected]) => { + test(`match some (one kind: ${kind})`, () => { + const query: PythonLocatorQuery = { kinds: [kind] }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + }); + + test('match some (many kinds)', () => { + const expected = [env6, envL1, envL2, envSL1, envSL2, envSL3, envSL4, envSL5]; + const kinds = [ + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnv, + PythonEnvKind.Pipenv, + PythonEnvKind.Conda, + PythonEnvKind.Custom, + ]; + const query: PythonLocatorQuery = { kinds }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match all', () => { + const kinds: PythonEnvKind[] = getEnumValues(PythonEnvKind); + const query: PythonLocatorQuery = { kinds }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, envs); + }); + }); + + suite('searchLocations', () => { + test('match none', () => { + const query: PythonLocatorQuery = { + searchLocations: { + roots: [doesNotExist], + }, + }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, []); + }); + + test('match one (multiple locations)', () => { + const expected = [envSL4]; + const searchLocations = { + roots: [ + envSL4.searchLocation!, + doesNotExist, + envSL4.searchLocation!, // repeated + ], + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match multiple (one location)', () => { + const expected = [envS3, envSL2]; + const searchLocations = { + roots: [ + workspaceRoot, + ], + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match multiple (multiple locations)', () => { + const expected = [envS3, ...rootedLocatedEnvs]; + const searchLocations = { + roots: rootedLocatedEnvs.map((env) => env.searchLocation!), + }; + searchLocations.roots.push(doesNotExist); + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match multiple (include non-searched envs)', () => { + const expected = [...plainEnvs, ...locatedEnvs, envS3, ...rootedLocatedEnvs]; + const searchLocations = { + roots: rootedLocatedEnvs.map((env) => env.searchLocation!), + includeNonRooted: true, + }; + searchLocations.roots.push(doesNotExist); + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match all searched', () => { + const expected = [...rootedEnvs, ...rootedLocatedEnvs]; + const searchLocations = { + roots: expected.map((env) => env.searchLocation!), + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match all (including non-searched)', () => { + const expected = envs; + const searchLocations = { + roots: expected.map((e) => e.searchLocation!).filter((e) => !!e), + includeNonRooted: true, + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match all searched under one root', () => { + const expected = [envS1, envS2, envSL1, envSL3, envSL5]; + const searchLocations = { + roots: [Uri.file(homeDir)], + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match only non-searched envs (empty roots)', () => { + const expected = [...plainEnvs, ...locatedEnvs]; + const searchLocations = { + roots: [], + includeNonRooted: true, + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match only non-searched envs (with unmatched location)', () => { + const expected = [...plainEnvs, ...locatedEnvs]; + const searchLocations = { + roots: [doesNotExist], + includeNonRooted: true, + }; + const query: PythonLocatorQuery = { searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + }); + + suite('mixed query', () => { + test('match none', () => { + const query: PythonLocatorQuery = { + kinds: [PythonEnvKind.OtherGlobal], + searchLocations: { + roots: [doesNotExist], + }, + }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, []); + }); + + test('match some', () => { + const expected = [envSL1, envSL4, envSL5]; + const kinds = [PythonEnvKind.Venv, PythonEnvKind.Custom]; + const searchLocations = { + roots: rootedLocatedEnvs.map((env) => env.searchLocation!), + }; + searchLocations.roots.push(doesNotExist); + const query: PythonLocatorQuery = { kinds, searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + + test('match all', () => { + const expected = [...rootedEnvs, ...rootedLocatedEnvs]; + const kinds: PythonEnvKind[] = getEnumValues(PythonEnvKind); + const searchLocations = { + roots: expected.map((env) => env.searchLocation!), + }; + const query: PythonLocatorQuery = { kinds, searchLocations }; + + const filter = getQueryFilter(query); + const filtered = envs.filter(filter); + + assert.deepEqual(filtered, expected); + }); + }); +}); + +suite('Python envs locator utils - getEnvs', () => { + test('empty, no update emitter', async () => { + const iterator = (async function* () { + // Yield nothing. + }()) as IPythonEnvsIterator; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, []); + }); + + test('empty, with unused update emitter', async () => { + const emitter = new EventEmitter(); + // eslint-disable-next-line require-yield + const iterator = (async function* () { + // Yield nothing. + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, []); + }); + + test('yield one, no update emitter', async () => { + const iterator = (async function* () { + yield env1; + }()) as IPythonEnvsIterator; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, [env1]); + }); + + test('yield one, no update', async () => { + const emitter = new EventEmitter(); + const iterator = (async function* () { + yield env1; + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, [env1]); + }); + + test('yield one, with update', async () => { + const expected = [envSL2]; + const old = copyEnvInfo(envSL2, { kind: PythonEnvKind.Venv }); + const emitter = new EventEmitter(); + const iterator = (async function* () { + yield old; + emitter.fire({ index: 0, old, update: envSL2 }); + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, expected); + }); + + test('yield many, no update emitter', async () => { + const expected = rootedLocatedEnvs; + const iterator = (async function* () { + yield* expected; + }()) as IPythonEnvsIterator; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, expected); + }); + + test('yield many, none updated', async () => { + const expected = rootedLocatedEnvs; + const emitter = new EventEmitter(); + const iterator = (async function* () { + yield* expected; + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, expected); + }); + + test('yield many, some updated', async () => { + const expected = rootedLocatedEnvs; + const emitter = new EventEmitter(); + const iterator = (async function* () { + const original = [...expected]; + const updated = [1, 2, 4]; + const kind = PythonEnvKind.Unknown; + updated.forEach((index) => { + original[index] = copyEnvInfo(expected[index], { kind }); + }); + + yield* original; + + updated.forEach((index) => { + emitter.fire({ index, old: original[index], update: expected[index] }); + }); + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, expected); + }); + + test('yield many, all updated', async () => { + const expected = rootedLocatedEnvs; + const emitter = new EventEmitter(); + const iterator = (async function* () { + const kind = PythonEnvKind.Unknown; + const original = expected.map((env) => copyEnvInfo(env, { kind })); + + yield original[0]; + yield original[1]; + emitter.fire({ index: 0, old: original[0], update: expected[0] }); + yield* original.slice(2); + original.forEach((old, index) => { + if (index > 0) { + emitter.fire({ index, old, update: expected[index] }); + } + }); + emitter.fire(null); + }()) as IPythonEnvsIterator; + iterator.onUpdated = emitter.event; + + const result = await getEnvs(iterator); + + assert.deepEqual(result, expected); + }); +}); diff --git a/src/test/pythonEnvironments/base/locators.unit.test.ts b/src/test/pythonEnvironments/base/locators.unit.test.ts index d7500fc37a05..4c76dfdff9e0 100644 --- a/src/test/pythonEnvironments/base/locators.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators.unit.test.ts @@ -8,7 +8,7 @@ import { PythonEnvInfo, PythonEnvKind } from '../../../client/pythonEnvironments import { PythonLocatorQuery } from '../../../client/pythonEnvironments/base/locator'; import { DisableableLocator, Locators } from '../../../client/pythonEnvironments/base/locators'; import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher'; -import { createEnv, createLocatedEnv, getEnvs, SimpleLocator } from './common'; +import { createLocatedEnv, createNamedEnv, getEnvs, SimpleLocator } from './common'; suite('Python envs locators - Locators', () => { suite('onChanged consolidates', () => { @@ -63,7 +63,7 @@ suite('Python envs locators - Locators', () => { }); test('one', async () => { - const env1 = createEnv('foo', '3.8', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env1]; const sub1 = new SimpleLocator(expected); const locators = new Locators([sub1]); @@ -75,11 +75,11 @@ suite('Python envs locators - Locators', () => { }); test('many', async () => { - const env1 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const env2 = createLocatedEnv('some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.System); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.System); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.System); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.System); const expected = [env1, env2, env3, env4, env5]; const sub1 = new SimpleLocator([env1]); const sub2 = new SimpleLocator([], { before: sub1.done }); @@ -96,14 +96,14 @@ suite('Python envs locators - Locators', () => { test('with query', async () => { const expected: PythonLocatorQuery = { kinds: [PythonEnvKind.Venv], - searchLocations: [Uri.file('???')] + searchLocations: { roots: [Uri.file('???')] }, }; let query: PythonLocatorQuery | undefined; async function onQuery(q: PythonLocatorQuery | undefined, e: PythonEnvInfo[]) { query = q; return e; } - const env1 = createEnv('foo', '3.8', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8', PythonEnvKind.Venv); const sub1 = new SimpleLocator([env1], { onQuery }); const locators = new Locators([sub1]); @@ -114,13 +114,13 @@ suite('Python envs locators - Locators', () => { }); test('iterate out of order', async () => { - const env1 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const env2 = createLocatedEnv('some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.System); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.System); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.Custom); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Custom); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.System); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.System); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.Custom); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Custom); const expected = [env5, env1, env2, env3, env4, env6, env7]; const sub4 = new SimpleLocator([env5]); const sub2 = new SimpleLocator([env1], { before: sub4.done }); @@ -136,11 +136,11 @@ suite('Python envs locators - Locators', () => { }); test('iterate intermingled', async () => { - const env1 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const env2 = createLocatedEnv('some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.System); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.System); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.System); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.System); const expected = [env1, env4, env2, env5, env3]; const deferred1 = createDeferred(); const deferred2 = createDeferred(); @@ -189,7 +189,7 @@ suite('Python envs locators - Locators', () => { suite('resolveEnv()', () => { test('one wrapped', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const calls: number[] = []; const sub1 = new SimpleLocator([env1], { resolve: async (e) => { @@ -205,7 +205,7 @@ suite('Python envs locators - Locators', () => { }); test('first one resolves', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const calls: number[] = []; const sub1 = new SimpleLocator([env1], { resolve: async (e) => { @@ -225,7 +225,7 @@ suite('Python envs locators - Locators', () => { }); test('second one resolves', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const calls: number[] = []; const sub1 = new SimpleLocator([env1], { resolve: async (_e) => { @@ -245,7 +245,7 @@ suite('Python envs locators - Locators', () => { }); test('none resolve', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const calls: number[] = []; const sub1 = new SimpleLocator([env1], { resolve: async (_e) => { calls.push(1); @@ -320,7 +320,7 @@ suite('Python envs locators - DisableableLocator', () => { suite('iterEnvs()', () => { test('pass-through if enabled', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = [env1]; const sub = new SimpleLocator([env1]); const locator = new DisableableLocator(sub); @@ -332,7 +332,7 @@ suite('Python envs locators - DisableableLocator', () => { }); test('empty if disabled', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = []; const sub = new SimpleLocator([env1]); const locator = new DisableableLocator(sub); @@ -347,7 +347,7 @@ suite('Python envs locators - DisableableLocator', () => { suite('resolveEnv()', () => { test('pass-through if enabled', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const sub = new SimpleLocator([env1]); const locator = new DisableableLocator(sub); @@ -358,7 +358,7 @@ suite('Python envs locators - DisableableLocator', () => { }); test('empty if disabled', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const sub = new SimpleLocator([env1]); const locator = new DisableableLocator(sub); 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 9bb67f672768..04434b21bc3b 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts @@ -13,16 +13,16 @@ import { } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer'; import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; import { sleep } from '../../../../core'; -import { createEnv, getEnvs, SimpleLocator } from '../../common'; +import { createNamedEnv, getEnvs, SimpleLocator } from '../../common'; -suite('Environments Reducer', () => { +suite('Python envs locator - Environments Reducer', () => { suite('iterEnvs()', () => { test('Iterator only yields unique environments', async () => { - const env1 = createEnv('env1', '3.5', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env2 = createEnv('env2', '3.8', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); // Same as env2 - const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1 + const env1 = createNamedEnv('env1', '3.5', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env2 = createNamedEnv('env2', '3.8', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env3 = createNamedEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createNamedEnv('env4', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); // Same as env2 + const env5 = createNamedEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1 const environmentsToBeIterated = [env1, env2, env3, env4, env5]; // Contains 3 unique environments const parentLocator = new SimpleLocator(environmentsToBeIterated); const reducer = new PythonEnvsReducer(parentLocator); @@ -36,11 +36,11 @@ suite('Environments Reducer', () => { test('Single updates for multiple environments are sent correctly followed by the null event', async () => { // Arrange - const env1 = createEnv('env1', '3.5', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); - const env2 = createEnv('env2', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); - const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); // Same as env2; - const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1; + const env1 = createNamedEnv('env1', '3.5', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); + const env2 = createNamedEnv('env2', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const env3 = createNamedEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createNamedEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); // Same as env2; + const env5 = createNamedEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); // Same as env1; const environmentsToBeIterated = [env1, env2, env3, env4, env5]; // Contains 3 unique environments const parentLocator = new SimpleLocator(environmentsToBeIterated); const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; @@ -64,8 +64,8 @@ suite('Environments Reducer', () => { // Assert const expectedUpdates = [ - { old: env2, new: mergeEnvironments(env2, env4) }, - { old: env1, new: mergeEnvironments(env1, env5) }, + { index: 1, old: env2, update: mergeEnvironments(env2, env4) }, + { index: 0, old: env1, update: mergeEnvironments(env1, env5) }, null, ]; assert.deepEqual(expectedUpdates, onUpdatedEvents); @@ -73,9 +73,9 @@ suite('Environments Reducer', () => { test('Multiple updates for the same environment are sent correctly followed by the null event', async () => { // Arrange - const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const env2 = createEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); - const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env3 = createNamedEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env1, env2, env3]; // All refer to the same environment const parentLocator = new SimpleLocator(environmentsToBeIterated); const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; @@ -102,17 +102,24 @@ suite('Environments Reducer', () => { const env123 = mergeEnvironments(env12, env3); const expectedUpdates: (PythonEnvUpdatedEvent | null)[] = []; if (isEqual(env12, env123)) { - expectedUpdates.push({ old: env1, new: env12 }, null); + expectedUpdates.push( + { index: 0, old: env1, update: env12 }, + null, + ); } else { - expectedUpdates.push({ old: env1, new: env12 }, { old: env12, new: env123 }, null); + expectedUpdates.push( + { index: 0, old: env1, update: env12 }, + { index: 0, old: env12, update: env123 }, + null, + ); } assert.deepEqual(onUpdatedEvents, expectedUpdates); }); test('Updates to environments from the incoming iterator are passed on correctly followed by the null event', async () => { // Arrange - const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const env2 = createEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env2', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env1]; const didUpdate = new EventEmitter(); const parentLocator = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event }); @@ -133,12 +140,15 @@ suite('Environments Reducer', () => { // Act await getEnvs(iterator); - didUpdate.fire({ old: env1, new: env2 }); + didUpdate.fire({ index: 0, old: env1, update: env2 }); didUpdate.fire(null); // It is essential for the incoming iterator to fire "null" event signifying it's done await sleep(1); // Assert - const expectedUpdates = [{ old: env1, new: mergeEnvironments(env1, env2) }, null]; + const expectedUpdates = [ + { index: 0, old: env1, update: mergeEnvironments(env1, env2) }, + null, + ]; assert.deepEqual(expectedUpdates, onUpdatedEvents); didUpdate.dispose(); }); @@ -162,21 +172,21 @@ suite('Environments Reducer', () => { 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 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const env2 = createEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); - const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env6 = createEnv('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env3 = createNamedEnv('env3', '3.8.1', PythonEnvKind.Conda, 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('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same const env13 = mergeEnvironments(env1, env3); const env136 = mergeEnvironments(env13, env6); - const expectedResolvedEnv = createEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/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 expectedResolvedEnv; + return expected; } return undefined; }, @@ -184,18 +194,18 @@ suite('Environments Reducer', () => { const reducer = new PythonEnvsReducer(parentLocator); // Trying to resolve the environment corresponding to env1 env3 env6 - const expected = await reducer.resolveEnv(path.join('path', 'to', 'exec')); + const resolved = await reducer.resolveEnv(path.join('path', 'to', 'exec')); - assert.deepEqual(expected, expectedResolvedEnv); + assert.deepEqual(resolved, expected); }); test("If the reducer isn't able to resolve environment, return undefined", async () => { - const env1 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const env2 = createEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env3 = createEnv('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); - const env4 = createEnv('env4', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env5 = createEnv('env5', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env6 = createEnv('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env2', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env3 = createNamedEnv('env3', '3.8.1', PythonEnvKind.Conda, 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('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env1, env2, env3, env4, env5, env6]; // env1 env3 env6 are same const env13 = mergeEnvironments(env1, env3); @@ -203,7 +213,7 @@ suite('Environments Reducer', () => { const parentLocator = new SimpleLocator(environmentsToBeIterated, { resolve: async (e: PythonEnvInfo) => { if (isEqual(e, env136)) { - return createEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); + return createNamedEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); } 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 b0a7d5bbf17b..d56468212826 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsResolver.unit.test.ts @@ -16,9 +16,9 @@ import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments import * as ExternalDep from '../../../../../client/pythonEnvironments/common/externalDependencies'; import { EnvironmentInfoService } from '../../../../../client/pythonEnvironments/info/environmentInfoService'; import { sleep } from '../../../../core'; -import { createEnv, getEnvs, SimpleLocator } from '../../common'; +import { createNamedEnv, getEnvs, SimpleLocator } from '../../common'; -suite('Environments Resolver', () => { +suite('Python envs locator - Environments Resolver', () => { /** * Returns the expected environment to be returned by Environment info service */ @@ -53,10 +53,10 @@ suite('Environments Resolver', () => { }); test('Iterator yields environments as-is', async () => { - const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); - const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); - const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); - const env4 = createEnv('env4', '3.9.0rc2', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const env1 = createNamedEnv('env1', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); + const env2 = createNamedEnv('env2', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2')); + const env3 = createNamedEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3')); + const env4 = createNamedEnv('env4', '3.9.0rc2', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); const environmentsToBeIterated = [env1, env2, env3, env4]; const parentLocator = new SimpleLocator(environmentsToBeIterated); const resolver = new PythonEnvsResolver(parentLocator, new EnvironmentInfoService()); @@ -69,8 +69,8 @@ suite('Environments Resolver', () => { test('Updates for environments are sent correctly followed by the null event', async () => { // Arrange - const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); - const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); + const env1 = createNamedEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); + const env2 = createNamedEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2')); const environmentsToBeIterated = [env1, env2]; const parentLocator = new SimpleLocator(environmentsToBeIterated); const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = []; @@ -94,8 +94,8 @@ suite('Environments Resolver', () => { // Assert const expectedUpdates = [ - { old: env1, new: createExpectedEnvInfo(env1) }, - { old: env2, new: createExpectedEnvInfo(env2) }, + { index: 0, old: env1, update: createExpectedEnvInfo(env1) }, + { index: 1, old: env2, update: createExpectedEnvInfo(env2) }, null, ]; assert.deepEqual(expectedUpdates, onUpdatedEvents); @@ -103,8 +103,8 @@ suite('Environments Resolver', () => { test('Updates to environments from the incoming iterator are sent correctly followed by the null event', async () => { // Arrange - const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const updatedEnv = createEnv('env1', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const updatedEnv = createNamedEnv('env1', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env]; const didUpdate = new EventEmitter(); const parentLocator = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event }); @@ -126,7 +126,7 @@ suite('Environments Resolver', () => { // Act await getEnvs(iterator); await sleep(1); - didUpdate.fire({ old: env, new: updatedEnv }); + didUpdate.fire({ index: 0, old: env, update: updatedEnv }); didUpdate.fire(null); // It is essential for the incoming iterator to fire "null" event signifying it's done await sleep(1); @@ -134,7 +134,7 @@ suite('Environments Resolver', () => { // The updates can be anything, even the number of updates, but they should lead to the same final state const { length } = onUpdatedEvents; assert.deepEqual( - onUpdatedEvents[length - 2]?.new, + onUpdatedEvents[length - 2]?.update, createExpectedEnvInfo(updatedEnv), 'The final update to environment is incorrect', ); @@ -179,8 +179,8 @@ suite('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 = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const resolvedEnvReturnedByReducer = createEnv( + const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const resolvedEnvReturnedByReducer = createNamedEnv( 'env1', '3.8.1', PythonEnvKind.Conda, @@ -207,8 +207,8 @@ suite('Environments Resolver', () => { reject(); }), ); - const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); - const resolvedEnvReturnedByReducer = createEnv( + const env = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const resolvedEnvReturnedByReducer = createNamedEnv( 'env1', '3.8.1', PythonEnvKind.Conda, @@ -230,7 +230,7 @@ suite('Environments Resolver', () => { }); test("If the parent locator isn't able to resolve environment, return undefined", async () => { - const env = createEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env = createNamedEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); const parentLocator = new SimpleLocator([], { resolve: async () => undefined, }); diff --git a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts index 2480aeed31d8..5dfed7117978 100644 --- a/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/index.unit.test.ts @@ -36,7 +36,10 @@ import { } from '../../../../client/pythonEnvironments/discovery/locators'; import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; import { - createEnv, createLocatedEnv, getEnvs, SimpleLocator, + createLocatedEnv, + createNamedEnv, + getEnvs, + SimpleLocator, } from '../../base/common'; class WorkspaceFolders { @@ -97,7 +100,7 @@ suite('WorkspaceLocators', () => { suite('onChanged', () => { test('no roots', () => { const expected: PythonEnvsChangedEvent[] = []; - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([ () => [loc1], @@ -115,7 +118,7 @@ suite('WorkspaceLocators', () => { test('no factories', () => { const expected: PythonEnvsChangedEvent[] = []; - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([]); const folders = new WorkspaceFolders(['foo', 'bar']); @@ -146,7 +149,7 @@ suite('WorkspaceLocators', () => { const event4: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; const event5: PythonEnvsChangedEvent = { kind: PythonEnvKind.Pipenv }; const event6: PythonEnvsChangedEvent = { kind: PythonEnvKind.Conda }; - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([]); const loc3 = new SimpleLocator([]); @@ -263,7 +266,7 @@ suite('WorkspaceLocators', () => { suite('iterEnvs()', () => { test('no roots', async () => { const expected: PythonEnvInfo[] = []; - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([ () => [loc1], @@ -307,7 +310,7 @@ suite('WorkspaceLocators', () => { test('one not empty', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env1]; const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([ @@ -324,8 +327,8 @@ suite('WorkspaceLocators', () => { test('empty locator ignored', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); const expected: PythonEnvInfo[] = [env1, env2]; const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([], { before: loc1.done }); @@ -345,14 +348,14 @@ suite('WorkspaceLocators', () => { test('consolidates envs across roots', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; const loc1 = new SimpleLocator([env1, env2]); const loc2 = new SimpleLocator([env3, env4], { before: loc1.done }); @@ -374,10 +377,10 @@ suite('WorkspaceLocators', () => { test('query matches a root', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); const expected: PythonEnvInfo[] = [env1, env2]; const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([env2], { before: loc1.done }); @@ -389,8 +392,9 @@ suite('WorkspaceLocators', () => { ]); const folders = new WorkspaceFolders([root1, root2]); locators.activate(folders); + const query = { searchLocations: { roots: [root1] } }; - const iterators = locators.iterEnvs({ searchLocations: [root1] }); + const iterators = locators.iterEnvs(query); const envs = await getEnvs(iterators); expect(envs).to.deep.equal(expected); @@ -399,10 +403,10 @@ suite('WorkspaceLocators', () => { test('query matches all roots', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); const expected: PythonEnvInfo[] = [env1, env2, env3, env4]; const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([env2], { before: loc1.done }); @@ -414,8 +418,9 @@ suite('WorkspaceLocators', () => { ]); const folders = new WorkspaceFolders([root1, root2]); locators.activate(folders); + const query = { searchLocations: { roots: [root1, root2] } }; - const iterators = locators.iterEnvs({ searchLocations: [root1, root2] }); + const iterators = locators.iterEnvs(query); const envs = await getEnvs(iterators); expect(envs).to.deep.equal(expected); @@ -424,10 +429,10 @@ suite('WorkspaceLocators', () => { test('query does not match a root', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([env2], { before: loc1.done }); const loc3 = new SimpleLocator([env3], { before: loc2.done }); @@ -438,8 +443,9 @@ suite('WorkspaceLocators', () => { ]); const folders = new WorkspaceFolders([root1, root2]); locators.activate(folders); + const query = { searchLocations: { roots: [Uri.file('baz')] } }; - const iterators = locators.iterEnvs({ searchLocations: [Uri.file('baz')] }); + const iterators = locators.iterEnvs(query); const envs = await getEnvs(iterators); expect(envs).to.deep.equal([]); @@ -448,10 +454,10 @@ suite('WorkspaceLocators', () => { test('query has no searchLocation', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); const expected: PythonEnvInfo[] = [env1, env2, env3, env4]; const loc1 = new SimpleLocator([env1]); const loc2 = new SimpleLocator([env2], { before: loc1.done }); @@ -473,14 +479,14 @@ suite('WorkspaceLocators', () => { test('iterate out of order', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env5, env6, env1, env2, env3, env4, env7, env8]; const loc3 = new SimpleLocator([env5, env6]); const loc1 = new SimpleLocator([env1, env2], { before: loc3.done }); @@ -502,14 +508,14 @@ suite('WorkspaceLocators', () => { test('iterate intermingled', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo-x', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo-x', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo-y', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo-y', '3.5.12b1', PythonEnvKind.Venv); const expected = [env3, env6, env1, env2, env8, env4, env5, env7]; const ordered = [env1, env2, env3, env4, env5, env6, env7, env8]; const deferreds = [ @@ -554,14 +560,14 @@ suite('WorkspaceLocators', () => { test('respects roots set during activation', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; const loc1 = new SimpleLocator([env1, env2]); const loc2 = new SimpleLocator([env3, env4], { before: loc1.done }); @@ -586,14 +592,14 @@ suite('WorkspaceLocators', () => { test('respects added roots', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; const loc1 = new SimpleLocator([env1, env2]); const loc2 = new SimpleLocator([env3, env4], { before: loc1.done }); @@ -620,14 +626,14 @@ suite('WorkspaceLocators', () => { test('ignores removed roots', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createEnv('foo', '3.5.12b1', PythonEnvKind.Venv); + const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); + const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); + const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); + const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); + const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); + const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); const expectedBefore = [env1, env2, env3, env4, env5, env6, env7, env8]; const expectedAfter = [env1, env2, env3, env4]; const loc1 = new SimpleLocator([env1, env2]); @@ -661,7 +667,7 @@ suite('WorkspaceLocators', () => { } test('no roots', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([ () => [loc1], @@ -675,7 +681,7 @@ suite('WorkspaceLocators', () => { }); test('no factories', async () => { - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const locators = new WorkspaceLocators([]); const folders = new WorkspaceFolders(['foo', 'bar']); locators.activate(folders); @@ -687,7 +693,7 @@ suite('WorkspaceLocators', () => { test('one locator, not resolved', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const loc1 = new SimpleLocator([env1], { resolve: null }); const locators = new WorkspaceLocators([ () => [loc1], @@ -702,7 +708,7 @@ suite('WorkspaceLocators', () => { test('one locator, resolved', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const loc1 = new SimpleLocator([env1]); const locators = new WorkspaceLocators([ @@ -718,7 +724,7 @@ suite('WorkspaceLocators', () => { test('one root, first locator resolves', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); const expected = env1; const seen: number[] = []; const loc1 = new SimpleLocator([env1], { resolve: getResolver(seen, 1) }); @@ -737,7 +743,7 @@ suite('WorkspaceLocators', () => { test('one root, second locator resolves', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + 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) }); @@ -756,7 +762,7 @@ suite('WorkspaceLocators', () => { test('one root, not resolved', async () => { const root1 = Uri.file('foo'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + 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) }); @@ -775,7 +781,7 @@ suite('WorkspaceLocators', () => { test('many roots, no searchLocation, second root matches', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + 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) }); @@ -795,7 +801,7 @@ suite('WorkspaceLocators', () => { test('many roots, searchLocation matches', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); env1.searchLocation = root2; const expected = env1; const seen: number[] = []; @@ -816,7 +822,7 @@ suite('WorkspaceLocators', () => { test('many roots, searchLocation does not match', async () => { const root1 = Uri.file('foo'); const root2 = Uri.file('bar'); - const env1 = createEnv('foo', '3.8.1', PythonEnvKind.Venv); + const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); env1.searchLocation = Uri.file('baz'); const expected = env1; const seen: number[] = [];