diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index bb72186ca7a5..2d1ebbb33bc4 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -37,6 +37,8 @@ export function buildEnvInfo(init?: { }, name: '', location: '', + searchLocation: undefined, + defaultDisplayName: undefined, version: { major: -1, minor: -1, @@ -109,7 +111,9 @@ export function getMinimalPartialInfo(env: string | Partial): Par return undefined; } return { - executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 }, + executable: { + filename: env, sysPrefix: '', ctime: -1, mtime: -1, + }, }; } if (env.executable === undefined) { @@ -136,7 +140,7 @@ export function getMinimalPartialInfo(env: string | Partial): Par export function areSameEnv( left: string | Partial, right: string | Partial, - allowPartialMatch?: boolean, + allowPartialMatch = true, ): boolean | undefined { const leftInfo = getMinimalPartialInfo(left); const rightInfo = getMinimalPartialInfo(right); @@ -173,7 +177,7 @@ export function areSameEnv( * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getPythonVersionInfoHeuristic(version:PythonVersion): number { +function getPythonVersionInfoHeuristic(version: PythonVersion): number { let infoLevel = 0; if (version.major > 0) { infoLevel += 20; // W4 @@ -205,7 +209,7 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number { * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getFileInfoHeuristic(file:FileInfo): number { +function getFileInfoHeuristic(file: FileInfo): number { let infoLevel = 0; if (file.filename.length > 0) { infoLevel += 5; // W2 @@ -229,7 +233,7 @@ function getFileInfoHeuristic(file:FileInfo): number { * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getDistroInfoHeuristic(distro:PythonDistroInfo):number { +function getDistroInfoHeuristic(distro: PythonDistroInfo): number { let infoLevel = 0; if (distro.org.length > 0) { infoLevel += 20; // W3 @@ -250,62 +254,6 @@ function getDistroInfoHeuristic(distro:PythonDistroInfo):number { return infoLevel; } -/** - * Gets a prioritized list of environment types for identification. - * @returns {PythonEnvKind[]} : List of environments ordered by identification priority - * - * Remarks: This is the order of detection based on how the various distributions and tools - * configure the environment, and the fall back for identification. - * Top level we have the following environment types, since they leave a unique signature - * in the environment or * use a unique path for the environments they create. - * 1. Conda - * 2. Windows Store - * 3. PipEnv - * 4. Pyenv - * 5. Poetry - * - * Next level we have the following virtual environment tools. The are here because they - * are consumed by the tools above, and can also be used independently. - * 1. venv - * 2. virtualenvwrapper - * 3. virtualenv - * - * Last category is globally installed python, or system python. - */ -export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { - return [ - PythonEnvKind.CondaBase, - PythonEnvKind.Conda, - PythonEnvKind.WindowsStore, - PythonEnvKind.Pipenv, - PythonEnvKind.Pyenv, - PythonEnvKind.Poetry, - PythonEnvKind.Venv, - PythonEnvKind.VirtualEnvWrapper, - PythonEnvKind.VirtualEnv, - PythonEnvKind.OtherVirtual, - PythonEnvKind.OtherGlobal, - PythonEnvKind.MacDefault, - PythonEnvKind.System, - PythonEnvKind.Custom, - PythonEnvKind.Unknown, - ]; -} - -/** - * Selects an environment based on the environment selection priority. This should - * match the priority in the environment identifier. - */ -export function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] { - // tslint:disable-next-line: no-suspicious-comment - // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have - // one location where we define priority and - const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); - return envs.sort( - (a:PythonEnvInfo, b:PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), - ); -} - /** * Merges properties of the `target` environment and `other` environment and returns the merged environment. * if the value in the `target` environment is not defined or has less information. This does not mutate @@ -318,18 +266,19 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo): const version = cloneDeep( getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version) - ? target.version : other.version, + ? target.version + : other.version, ); const executable = cloneDeep( getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable) - ? target.executable : other.executable, + ? target.executable + : other.executable, ); executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix; const distro = cloneDeep( - getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) - ? target.distro : other.distro, + getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) ? target.distro : other.distro, ); merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch; @@ -341,8 +290,8 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo): // preferred env based on kind. merged.kind = target.kind; - merged.location = merged.location ?? other.location; - merged.name = merged.name ?? other.name; + merged.location = merged.location.length ? merged.location : other.location; + merged.name = merged.name.length ? merged.name : other.name; merged.searchLocation = merged.searchLocation ?? other.searchLocation; merged.version = version; diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts index 8e10c163b666..6b24ceac01b1 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { cloneDeep, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; import { Event, EventEmitter } from 'vscode'; import { traceVerbose } from '../../../../common/logger'; -import { createDeferred } from '../../../../common/utils/async'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; -import { areSameEnv } from '../../info/env'; +import { areSameEnv, mergeEnvironments } from '../../info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, } from '../../locator'; +import { getEnvs } from '../../locatorUtils'; import { PythonEnvsChangedEvent } from '../../watcher'; /** @@ -23,27 +23,11 @@ export class PythonEnvsReducer implements ILocator { constructor(private readonly parentLocator: ILocator) {} public async resolveEnv(env: string | PythonEnvInfo): Promise { - let environment: PythonEnvInfo | undefined; - const waitForUpdatesDeferred = createDeferred(); - const iterator = this.iterEnvs(); - iterator.onUpdated!((event) => { - if (event === null) { - waitForUpdatesDeferred.resolve(); - } else if (environment && areSameEnv(environment, event.update)) { - environment = event.update; - } - }); - let result = await iterator.next(); - while (!result.done) { - if (areSameEnv(result.value, env)) { - environment = result.value; - } - result = await iterator.next(); - } + const environments = await getEnvs(this.iterEnvs()); + const environment = environments.find((e) => areSameEnv(e, env)); if (!environment) { return undefined; } - await waitForUpdatesDeferred.promise; return this.parentLocator.resolveEnv(environment); } @@ -73,8 +57,7 @@ async function* iterEnvsIterator( checkIfFinishedAndNotify(state, didUpdate); } else if (seen[event.index] !== undefined) { state.pending += 1; - resolveDifferencesInBackground(event.index, event.update, 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} (#${event.index})`); @@ -110,7 +93,7 @@ async function resolveDifferencesInBackground( seen: PythonEnvInfo[], ) { const oldEnv = seen[oldIndex]; - const merged = mergeEnvironments(oldEnv, newEnv); + const merged = resolveEnvCollision(oldEnv, newEnv); if (!isEqual(oldEnv, merged)) { seen[oldIndex] = merged; didUpdate.fire({ index: oldIndex, old: oldEnv, update: merged }); @@ -134,29 +117,63 @@ function checkIfFinishedAndNotify( } } -export function mergeEnvironments(environment: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo { - const result = cloneDeep(environment); - // Preserve type information. - // Possible we identified environment as unknown, but a later provider has identified env type. - if (environment.kind === PythonEnvKind.Unknown && other.kind && other.kind !== PythonEnvKind.Unknown) { - result.kind = other.kind; - } - const props: (keyof PythonEnvInfo)[] = [ - 'version', - 'kind', - 'executable', - 'name', - 'arch', - 'distro', - 'defaultDisplayName', - 'searchLocation', +function resolveEnvCollision(oldEnv: PythonEnvInfo, newEnv: PythonEnvInfo): PythonEnvInfo { + const [env, other] = sortEnvInfoByPriority(oldEnv, newEnv); + return mergeEnvironments(env, other); +} + +/** + * Selects an environment based on the environment selection priority. This should + * match the priority in the environment identifier. + */ +function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] { + // tslint:disable-next-line: no-suspicious-comment + // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have + // one location where we define priority. + const envKindByPriority: PythonEnvKind[] = getPrioritizedEnvironmentKind(); + return envs.sort( + (a: PythonEnvInfo, b: PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), + ); +} + +/** + * Gets a prioritized list of environment types for identification. + * @returns {PythonEnvKind[]} : List of environments ordered by identification priority + * + * Remarks: This is the order of detection based on how the various distributions and tools + * configure the environment, and the fall back for identification. + * Top level we have the following environment types, since they leave a unique signature + * in the environment or * use a unique path for the environments they create. + * 1. Conda + * 2. Windows Store + * 3. PipEnv + * 4. Pyenv + * 5. Poetry + * + * Next level we have the following virtual environment tools. The are here because they + * are consumed by the tools above, and can also be used independently. + * 1. venv + * 2. virtualenvwrapper + * 3. virtualenv + * + * Last category is globally installed python, or system python. + */ +function getPrioritizedEnvironmentKind(): PythonEnvKind[] { + return [ + PythonEnvKind.CondaBase, + PythonEnvKind.Conda, + PythonEnvKind.WindowsStore, + PythonEnvKind.Pipenv, + PythonEnvKind.Pyenv, + PythonEnvKind.Poetry, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.VirtualEnv, + PythonEnvKind.OtherVirtual, + PythonEnvKind.OtherGlobal, + PythonEnvKind.MacDefault, + PythonEnvKind.System, + PythonEnvKind.Custom, + PythonEnvKind.Unknown, ]; - props.forEach((prop) => { - if (!result[prop] && other[prop]) { - // tslint:disable: no-any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (result as any)[prop] = other[prop]; - } - }); - return result; } diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 1e78ed20f59d..8bf38355cabf 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable: no-this-assignment + import * as path from 'path'; import { Event } from 'vscode'; import { @@ -8,13 +10,18 @@ import { } from '../../../client/common/utils/async'; import { Architecture } from '../../../client/common/utils/platform'; import { + PythonDistroInfo, PythonEnvInfo, PythonEnvKind, + PythonExecutableInfo, } 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, + IPythonEnvsIterator, + Locator, + PythonEnvUpdatedEvent, + PythonLocatorQuery, } from '../../../client/pythonEnvironments/base/locator'; import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher'; @@ -22,16 +29,26 @@ export function createLocatedEnv( locationStr: string, versionStr: string, kind = PythonEnvKind.Unknown, - execStr = 'python', + exec: string | PythonExecutableInfo = 'python', + distro: PythonDistroInfo = { org: '' }, ): PythonEnvInfo { const location = locationStr === '' ? '' : path.normalize(locationStr); - const normalizedExecutable = path.normalize(execStr); - const executable = location === '' || path.isAbsolute(normalizedExecutable) - ? normalizedExecutable - : path.join(location, 'bin', normalizedExecutable); + let executable: string | undefined; + if (typeof exec === 'string') { + const normalizedExecutable = path.normalize(exec); + executable = location === '' || path.isAbsolute(normalizedExecutable) + ? normalizedExecutable + : path.join(location, 'bin', normalizedExecutable); + } const version = parseVersion(versionStr); - const env = buildEnvInfo({ kind, executable, location, version }); + const env = buildEnvInfo({ + kind, executable, location, version, + }); env.arch = Architecture.x86; + env.distro = distro; + if (typeof exec !== 'string') { + env.executable = exec; + } return env; } @@ -39,15 +56,17 @@ export function createNamedEnv( name: string, versionStr: string, kind?: PythonEnvKind, - execStr = 'python', + exec: string | PythonExecutableInfo = 'python', + distro?: PythonDistroInfo, ): PythonEnvInfo { - const env = createLocatedEnv('', versionStr, kind, execStr); + const env = createLocatedEnv('', versionStr, kind, exec, distro); env.name = name; return env; } export class SimpleLocator extends Locator { private deferred = createDeferred(); + constructor( private envs: PythonEnvInfo[], public callbacks: { @@ -62,18 +81,21 @@ export class SimpleLocator extends Locator { ) { super(); } + public get done(): Promise { return this.deferred.promise; } - public fire(event: PythonEnvsChangedEvent) { + + public fire(event: PythonEnvsChangedEvent): void { this.emitter.fire(event); } + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - const deferred = this.deferred; - const callbacks = this.callbacks; - let envs = this.envs; - const iterator: IPythonEnvsIterator = async function*() { - if (callbacks.onQuery !== undefined) { + const { deferred } = this; + const { callbacks } = this; + let { envs } = this; + const iterator: IPythonEnvsIterator = (async function* () { + if (callbacks?.onQuery !== undefined) { envs = await callbacks.onQuery(query, envs); } if (callbacks.before !== undefined) { @@ -99,23 +121,23 @@ export class SimpleLocator extends Locator { } } } - if (callbacks.after!== undefined) { + if (callbacks?.after !== undefined) { await callbacks.after; } deferred.resolve(); - }(); - iterator.onUpdated = this.callbacks.onUpdated; + }()); + iterator.onUpdated = this.callbacks?.onUpdated; return iterator; } + public async resolveEnv(env: string | PythonEnvInfo): Promise { const envInfo: PythonEnvInfo = typeof env === 'string' ? createLocatedEnv('', '', undefined, env) : env; if (this.callbacks.resolve === undefined) { return envInfo; - } else if (this.callbacks.resolve === null) { + } if (this.callbacks?.resolve === null) { return undefined; - } else { - return this.callbacks.resolve(envInfo); } + return this.callbacks.resolve(envInfo); } } 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 04434b21bc3b..2f1e0310e8b8 100644 --- a/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts @@ -7,10 +7,7 @@ import * as path from 'path'; import { EventEmitter } from 'vscode'; import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator'; -import { - mergeEnvironments, - PythonEnvsReducer, -} from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer'; +import { PythonEnvsReducer } from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer'; import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; import { sleep } from '../../../../core'; import { createNamedEnv, getEnvs, SimpleLocator } from '../../common'; @@ -36,11 +33,45 @@ suite('Python envs locator - Environments Reducer', () => { test('Single updates for multiple environments are sent correctly followed by the null event', async () => { // Arrange - 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 env1 = createNamedEnv('env15', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1')); + const env2 = createNamedEnv( + 'env24', + '3.8', + PythonEnvKind.Unknown, + { + filename: path.join('path', 'to', 'folder', 'python3.8'), + ctime: 15, + mtime: -1, + sysPrefix: '', + }, + { + org: 'OrgName', + defaultDisplayName: 'Default name', + }, + ); 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 env4 = createNamedEnv( + '', + '3.8.1', + PythonEnvKind.Conda, + { + filename: path.join('path', 'to', 'folder', 'python'), + ctime: -1, + mtime: 15, + sysPrefix: 'sysPrefix', + }, + { + org: 'Some other orgName', + binDir: 'path/to/binDir', + version: { + raw: 'Raw version', + major: 3, + minor: -1, + micro: 2, + }, + }, + ); // Same as env2 + const env5 = createNamedEnv('env15', '3.5', 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)[] = []; @@ -63,9 +94,33 @@ suite('Python envs locator - Environments Reducer', () => { await sleep(1); // Resolve pending calls in the background // Assert + // Note merged env is constructed picking the better fields from the two envs. + // For eg. the merge for env2 & env4 should be, + const env24 = createNamedEnv( + // Pick name from env2 + 'env24', + // Choose version from env4 + '3.8.1', + // Choose type from env4 + PythonEnvKind.Conda, + { + // Choose file info from env2 + filename: path.join('path', 'to', 'folder', 'python3.8'), + ctime: 15, + mtime: -1, + // Choose sysPrefix from env4 + sysPrefix: 'sysPrefix', + }, + // Choose distro info from env2 + { + org: 'OrgName', + defaultDisplayName: 'Default name', + }, + ); + const env15 = createNamedEnv('env15', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1')); const expectedUpdates = [ - { index: 1, old: env2, update: mergeEnvironments(env2, env4) }, - { index: 0, old: env1, update: mergeEnvironments(env1, env5) }, + { index: 1, old: env2, update: env24 }, + { index: 0, old: env1, update: env15 }, null, ]; assert.deepEqual(expectedUpdates, onUpdatedEvents); @@ -73,9 +128,9 @@ suite('Python envs locator - Environments Reducer', () => { test('Multiple updates for the same environment are sent correctly followed by the null event', async () => { // Arrange - 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 env1 = createNamedEnv('env123', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env123', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env3 = createNamedEnv('env123', '3.8', 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)[] = []; @@ -98,14 +153,11 @@ suite('Python envs locator - Environments Reducer', () => { await sleep(1); // Resolve pending calls in the background // Assert - const env12 = mergeEnvironments(env1, env2); - const env123 = mergeEnvironments(env12, env3); + const env12 = createNamedEnv('env123', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const env123 = createNamedEnv('env123', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); const expectedUpdates: (PythonEnvUpdatedEvent | null)[] = []; if (isEqual(env12, env123)) { - expectedUpdates.push( - { index: 0, old: env1, update: env12 }, - null, - ); + expectedUpdates.push({ index: 0, old: env1, update: env12 }, null); } else { expectedUpdates.push( { index: 0, old: env1, update: env12 }, @@ -118,8 +170,8 @@ suite('Python envs locator - Environments Reducer', () => { test('Updates to environments from the incoming iterator are passed on correctly followed by the null event', async () => { // Arrange - 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 env1 = createNamedEnv('env12', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + const env2 = createNamedEnv('env12', '3.8', PythonEnvKind.System, path.join('path', 'to', 'exec')); const environmentsToBeIterated = [env1]; const didUpdate = new EventEmitter(); const parentLocator = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event }); @@ -145,10 +197,8 @@ suite('Python envs locator - Environments Reducer', () => { await sleep(1); // Assert - const expectedUpdates = [ - { index: 0, old: env1, update: mergeEnvironments(env1, env2) }, - null, - ]; + const env12 = createNamedEnv('env12', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + const expectedUpdates = [{ index: 0, old: env1, update: env12 }, null]; assert.deepEqual(expectedUpdates, onUpdatedEvents); didUpdate.dispose(); }); @@ -172,23 +222,22 @@ suite('Python envs locator - 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 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + 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('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + 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('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + 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 env13 = mergeEnvironments(env1, env3); - const env136 = mergeEnvironments(env13, env6); + 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; } - return undefined; + throw new Error('Incorrect environment sent to the resolve'); }, }); const reducer = new PythonEnvsReducer(parentLocator); @@ -200,22 +249,21 @@ suite('Python envs locator - Environments Reducer', () => { }); test("If the reducer isn't able to resolve environment, return undefined", async () => { - const env1 = createNamedEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec')); + 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('env3', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); + 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('env6', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec')); + 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 env13 = mergeEnvironments(env1, env3); - const env136 = mergeEnvironments(env13, env6); + const env136 = createNamedEnv('env', '3.8.1b1', PythonEnvKind.Conda, path.join('path', 'to', 'exec')); const parentLocator = new SimpleLocator(environmentsToBeIterated, { resolve: async (e: PythonEnvInfo) => { if (isEqual(e, env136)) { return createNamedEnv('resolvedEnv', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec'); } - return undefined; + throw new Error('Incorrect environment sent to the resolve'); }, }); const reducer = new PythonEnvsReducer(parentLocator);