diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 687348964891..ab3b17629bc5 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -7,7 +7,6 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info'; import { - BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, PythonEnvCollectionChangedEvent, PythonEnvsChangedEvent, @@ -128,6 +127,14 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { * If provided, results should be limited to within these locations. */ searchLocations?: SearchLocations; + /** + * If provided, results should be limited envs provided by these locators. + */ + providerId?: string; + /** + * If provided, results area limited to this env. + */ + envPath?: string; }; type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; @@ -153,8 +160,8 @@ export type BasicEnvInfo = { * events emitted via `onChanged` do not need to provide information * for the specific environments that changed. */ -export interface ILocator - extends IPythonEnvsWatcher { +export interface ILocator extends IPythonEnvsWatcher { + readonly providerId: string; /** * Iterate over the enviroments known tos this locator. * @@ -174,6 +181,8 @@ export interface ILocator): IPythonEnvsIterator; } +export type ICompositeLocator = Omit, 'providerId'>; + interface IResolver { /** * Find as much info about the given Python environment as possible. @@ -184,7 +193,7 @@ interface IResolver { resolveEnv(path: string): Promise; } -export interface IResolvingLocator extends IResolver, ILocator {} +export interface IResolvingLocator extends IResolver, ICompositeLocator {} export interface GetRefreshEnvironmentsOptions { /** @@ -234,7 +243,7 @@ export interface IDiscoveryAPI { resolveEnv(path: string): Promise; } -interface IEmitter { +interface IEmitter { fire(e: E): void; } @@ -250,10 +259,11 @@ interface IEmitter { * should be used. Only in low-level cases should you consider using * `BasicPythonEnvsChangedEvent`. */ -abstract class LocatorBase - implements ILocator { +abstract class LocatorBase implements ILocator { public readonly onChanged: Event; + public abstract readonly providerId: string; + protected readonly emitter: IEmitter; constructor(watcher: IPythonEnvsWatcher & IEmitter) { diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 4c854e975ecf..10be15c27bf1 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -5,6 +5,7 @@ import { chain } from '../../common/utils/async'; import { Disposables } from '../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from './info'; import { + ICompositeLocator, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -59,12 +60,15 @@ export function combineIterators(iterators: IPythonEnvsIterator[]): IPytho * * Events and iterator results are combined. */ -export class Locators extends PythonEnvsWatchers implements ILocator { +export class Locators extends PythonEnvsWatchers implements ICompositeLocator { + public readonly providerId: string; + constructor( // The locators will be watched as well as iterated. private readonly locators: ReadonlyArray>, ) { super(locators); + this.providerId = locators.map((loc) => loc.providerId).join('+'); } public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index 2a524ad7d28f..c370c36ae190 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -5,8 +5,9 @@ import { IDisposable } from '../../../../common/types'; import { createDeferred, Deferred } from '../../../../common/utils/async'; import { Disposables } from '../../../../common/utils/resourceLifecycle'; import { traceError } from '../../../../logging'; -import { PythonEnvInfo } from '../../info'; -import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; +import { arePathsSame } from '../../../common/externalDependencies'; +import { getEnvPath } from '../../info/env'; +import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; /** * A base locator class that manages the lifecycle of resources. @@ -20,7 +21,7 @@ import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator' * * Otherwise it will leak (and we have no leak detection). */ -export abstract class LazyResourceBasedLocator extends Locator implements IDisposable { +export abstract class LazyResourceBasedLocator extends Locator implements IDisposable { protected readonly disposables = new Disposables(); // This will be set only once we have to create necessary resources @@ -42,15 +43,29 @@ export abstract class LazyResourceBasedLocator extends Locato await this.disposables.dispose(); } - public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { await this.activate(); - yield* this.doIterEnvs(query); + const iterator = this.doIterEnvs(query); + if (query?.envPath) { + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + const { path } = getEnvPath(currEnv.executablePath, currEnv.envPath); + if (arePathsSame(path, query.envPath)) { + yield currEnv; + break; + } + result = await iterator.next(); + } + } else { + yield* iterator; + } } /** * The subclass implementation of iterEnvs(). */ - protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; + protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; /** * This is where subclasses get their resources ready. diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index ca7c93b1c269..7bfaf900f689 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -57,7 +57,9 @@ export class EnvsCollectionService extends PythonEnvsWatcher { - const query = undefined; // We can also form a query based on the event, but skip that for simplicity. + const query: PythonLocatorQuery | undefined = event.providerId + ? { providerId: event.providerId, envPath: event.envPath } + : undefined; // We can also form a query based on the event, but skip that for simplicity. let scheduledRefresh = this.scheduledRefreshesPerQuery.get(query); // If there is no refresh scheduled for the query, start a new one. if (!scheduledRefresh) { diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts index 92d81243f97b..2f9bcea8e590 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -9,6 +9,7 @@ import { areSameEnv } from '../../info/env'; import { getPrioritizedEnvKinds } from '../../info/envKind'; import { BasicEnvInfo, + ICompositeLocator, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -22,7 +23,7 @@ import { PythonEnvsChangedEvent } from '../../watcher'; /** * Combines duplicate environments received from the incoming locator into one and passes on unique environments */ -export class PythonEnvsReducer implements ILocator { +export class PythonEnvsReducer implements ICompositeLocator { public get onChanged(): Event { return this.parentLocator.onChanged; } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 1baa3f36c993..13cacf337d07 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -10,7 +10,7 @@ import { getEnvPath, setEnvDisplayString } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { BasicEnvInfo, - ILocator, + ICompositeLocator, IPythonEnvsIterator, IResolvingLocator, isProgressEvent, @@ -35,7 +35,7 @@ export class PythonEnvsResolver implements IResolvingLocator { } constructor( - private readonly parentLocator: ILocator, + private readonly parentLocator: ICompositeLocator, private readonly environmentInfoService: IEnvironmentInfoService, ) {} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts index ed7848922893..2ef07fef0555 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts @@ -7,7 +7,9 @@ import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManag import { traceError, traceVerbose } from '../../../../logging'; import { FSWatchingLocator } from './fsWatchingLocator'; -export class CondaEnvironmentLocator extends FSWatchingLocator { +export class CondaEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'conda-envs'; + public constructor() { super( () => getCondaEnvironmentsTxt(), diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts index 172a57bb102f..06cbfe38ee3c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -78,7 +78,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'custom-virtual-envs'; + constructor() { super(getCustomVirtualEnvDirs, getVirtualEnvKind, { // Note detecting kind of virtual env depends on the file structure around the diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts index 9f75f6b6db15..e5ed206650ca 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts @@ -14,7 +14,9 @@ type GetExecutablesFunc = () => AsyncIterableIterator; /** * A naive locator the wraps a function that finds Python executables. */ -class FoundFilesLocator implements ILocator { +abstract class FoundFilesLocator implements ILocator { + public abstract readonly providerId: string; + public readonly onChanged: Event; protected readonly watcher = new PythonEnvsWatcher(); @@ -45,6 +47,8 @@ type GetDirExecutablesFunc = (dir: string) => AsyncIterableIterator; * A locator for executables in a single directory. */ export class DirFilesLocator extends FoundFilesLocator { + public readonly providerId: string; + constructor( dirname: string, defaultKind: PythonEnvKind, @@ -53,6 +57,7 @@ export class DirFilesLocator extends FoundFilesLocator { source?: PythonEnvSource[], ) { super(defaultKind, () => getExecutables(dirname), source); + this.providerId = `dir-files-${dirname}`; } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 6d2c93829906..0eb1d125200c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -13,7 +13,7 @@ import { resolvePythonExeGlobs, watchLocationForPythonBinaries, } from '../../../common/pythonBinariesWatcher'; -import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { PythonEnvKind } from '../../info'; import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; export enum FSWatcherKind { @@ -80,7 +80,7 @@ type FileWatchOptions = { * * Subclasses can call `this.emitter.fire()` * to emit events. */ -export abstract class FSWatchingLocator extends LazyResourceBasedLocator { +export abstract class FSWatchingLocator extends LazyResourceBasedLocator { constructor( /** * Location(s) to watch for python binaries. @@ -135,7 +135,7 @@ export abstract class FSWatchingLocator extends LazyResourceB this.disposables.push( watchLocationForPattern(path.dirname(root), path.basename(root), () => { traceVerbose('Detected change in file: ', root, 'initiating a refresh'); - this.emitter.fire({}); + this.emitter.fire({ providerId: this.providerId }); }), ); return; @@ -161,7 +161,7 @@ export abstract class FSWatchingLocator extends LazyResourceB // |__ python <--- executable const searchLocation = Uri.file(opts.searchLocation ?? path.dirname(getEnvironmentDirFromPath(executable))); traceVerbose('Fired event ', JSON.stringify({ type, kind, searchLocation }), 'from locator'); - this.emitter.fire({ type, kind, searchLocation }); + this.emitter.fire({ type, kind, searchLocation, providerId: this.providerId, envPath: executable }); }; const globs = resolvePythonExeGlobs( diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts index e215cdb2a3dd..cddec23f7b0b 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts @@ -82,7 +82,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'global-virtual-env'; + constructor(private readonly searchDepth?: number) { super(getGlobalVirtualEnvDirs, getVirtualEnvKind, { // Note detecting kind of virtual env depends on the file structure around the diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts index d04d85148479..6b8e5ac0084c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts @@ -65,7 +65,9 @@ export async function getMicrosoftStorePythonExes(): Promise { return []; } -export class MicrosoftStoreLocator extends FSWatchingLocator { +export class MicrosoftStoreLocator extends FSWatchingLocator { + public readonly providerId: string = 'microsoft-store'; + private readonly kind: PythonEnvKind = PythonEnvKind.MicrosoftStore; constructor() { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts index e37e64347983..5df4f366e86d 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts @@ -60,7 +60,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class PoetryLocator extends FSWatchingLocator { + public readonly providerId: string = 'poetry'; + public constructor(private readonly root: string) { super( () => getRootVirtualEnvDir(root), diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts index 02671d27c9bd..25923701c05a 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts @@ -12,6 +12,8 @@ import { isMacDefaultPythonPath } from './macDefaultLocator'; import { traceError } from '../../../../logging'; export class PosixKnownPathsLocator extends Locator { + public readonly providerId = 'posixKnownPaths'; + private kind: PythonEnvKind = PythonEnvKind.OtherGlobal; public iterEnvs(): IPythonEnvsIterator { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts index 7d6773c0058c..89346069772d 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts @@ -35,7 +35,9 @@ async function* getPyenvEnvironments(): AsyncIterableIterator { } } -export class PyenvLocator extends FSWatchingLocator { +export class PyenvLocator extends FSWatchingLocator { + public readonly providerId: string = 'pyenv'; + constructor() { super(getPyenvVersionsDir, async () => PythonEnvKind.Pyenv); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts index 0f520e8a8876..b7cb27875769 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts @@ -24,6 +24,8 @@ import { DirFilesLocator } from './filesLocator'; * it for changes. */ export class WindowsPathEnvVarLocator implements ILocator, IDisposable { + public readonly providerId: string = 'windows-path-env-var-locator'; + public readonly onChanged: Event; private readonly locators: Locators; @@ -93,6 +95,7 @@ function getDirFilesLocator( yield* await getEnvs(locator.iterEnvs(query)); } return { + providerId: locator.providerId, iterEnvs, dispose, onChanged: locator.onChanged, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts index 0d44e3c3699e..cb59c1e49a60 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts @@ -7,6 +7,8 @@ import { getRegistryInterpreters } from '../../../common/windowsUtils'; import { traceError } from '../../../../logging'; export class WindowsRegistryLocator extends Locator { + public readonly providerId: string = 'windows-registry'; + // eslint-disable-next-line class-methods-use-this public iterEnvs(): IPythonEnvsIterator { const iterator = async function* () { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts index 751c9b97c162..127515607563 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -50,7 +50,9 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'workspaceVirtualEnvLocator'; + public constructor(private readonly root: string) { super( () => getWorkspaceVirtualEnvDirs(this.root), diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index fbc21fb44b21..bfaede584f6f 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -8,7 +8,7 @@ import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; import { Disposables } from '../../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from '../info'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; +import { BasicEnvInfo, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; @@ -30,13 +30,16 @@ export class ExtensionLocators extends Locators { public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators: IPythonEnvsIterator[] = [this.workspace.iterEnvs(query)]; if (!query?.searchLocations?.doNotIncludeNonRooted) { - iterators.push(...this.nonWorkspace.map((loc) => loc.iterEnvs(query))); + const nonWorkspace = query?.providerId + ? this.nonWorkspace.filter((locator) => query.providerId === locator.providerId) + : this.nonWorkspace; + iterators.push(...nonWorkspace.map((loc) => loc.iterEnvs(query))); } return combineIterators(iterators); } } -type WorkspaceLocatorFactoryResult = ILocator & Partial; -type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; +type WorkspaceLocatorFactoryResult = ILocator & Partial; +type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; type RootURI = string; export type WatchRootsArgs = { @@ -52,12 +55,14 @@ type WatchRootsFunc = (args: WatchRootsArgs) => IDisposable; * The factories are used to produce the locators for each workspace folder. */ -export class WorkspaceLocators extends LazyResourceBasedLocator { - private readonly locators: Record, IDisposable]> = {}; +export class WorkspaceLocators extends LazyResourceBasedLocator { + public readonly providerId: string = 'workspace-locators'; + + private readonly locators: Record, IDisposable]> = {}; private readonly roots: Record = {}; - constructor(private readonly watchRoots: WatchRootsFunc, private readonly factories: WorkspaceLocatorFactory[]) { + constructor(private readonly watchRoots: WatchRootsFunc, private readonly factories: WorkspaceLocatorFactory[]) { super(); this.activate().ignoreErrors(); } @@ -70,7 +75,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocat roots.forEach((root) => this.removeRoot(root)); } - protected doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + protected doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = Object.keys(this.locators).map((key) => { if (query?.searchLocations !== undefined) { const root = this.roots[key]; @@ -79,7 +84,11 @@ export class WorkspaceLocators extends LazyResourceBasedLocat // Ignore any requests for global envs. if (!query.searchLocations.roots.some(filter)) { // This workspace folder did not match the query, so skip it! - return iterEmpty(); + return iterEmpty(); + } + if (query.providerId && query.providerId !== this.providerId) { + // This is a request for a specific provider, so skip it. + return iterEmpty(); } } // The query matches or was not location-specific. @@ -108,7 +117,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocat private addRoot(root: Uri): void { // Create the root's locator, wrapping each factory-generated locator. - const locators: ILocator[] = []; + const locators: ILocator[] = []; const disposables = new Disposables(); this.factories.forEach((create) => { create(root).forEach((loc) => { diff --git a/src/client/pythonEnvironments/base/watcher.ts b/src/client/pythonEnvironments/base/watcher.ts index dfe998865a28..a9d0ef65595e 100644 --- a/src/client/pythonEnvironments/base/watcher.ts +++ b/src/client/pythonEnvironments/base/watcher.ts @@ -22,11 +22,20 @@ export type BasicPythonEnvsChangedEvent = { /** * The full set of possible info for a Python environments event. - * - * @prop searchLocation - the location, if any, affected by the event */ export type PythonEnvsChangedEvent = BasicPythonEnvsChangedEvent & { + /** + * The location, if any, affected by the event. + */ searchLocation?: Uri; + /** + * A specific provider, if any, affected by the event. + */ + providerId?: string; + /** + * The env, if any, affected by the event. + */ + envPath?: string; }; export type PythonEnvCollectionChangedEvent = BasicPythonEnvCollectionChangedEvent & { @@ -47,7 +56,7 @@ export type BasicPythonEnvCollectionChangedEvent = { * events, their source, and the timing is entirely up to the watcher * implementation. */ -export interface IPythonEnvsWatcher { +export interface IPythonEnvsWatcher { /** * The hook for registering event listeners (callbacks). */ diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index b4e9d45fa44a..79022dc09c30 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -106,7 +106,7 @@ async function createLocator( // This is shared. ): Promise { // Create the low-level locators. - let locators: ILocator = new ExtensionLocators( + const locators: ILocator = new ExtensionLocators( // Here we pull the locators together. createNonWorkspaceLocators(ext), createWorkspaceLocator(ext), @@ -116,9 +116,9 @@ async function createLocator( const envInfoService = getEnvironmentInfoService(ext.disposables); // Build the stack of composite locators. - locators = new PythonEnvsReducer(locators); + const reducer = new PythonEnvsReducer(locators); const resolvingLocator = new PythonEnvsResolver( - locators, + reducer, // These are shared. envInfoService, ); @@ -177,8 +177,8 @@ function watchRoots(args: WatchRootsArgs): IDisposable { }); } -function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { - const locators = new WorkspaceLocators(watchRoots, [ +function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { + const locators = new WorkspaceLocators(watchRoots, [ (root: vscode.Uri) => [new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath)], // Add an ILocator factory func here for each kind of workspace-rooted locator. ]); diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 603f423037bc..1b132df995e0 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -95,6 +95,8 @@ export function createNamedEnv( } export class SimpleLocator extends Locator { + public readonly providerId: string = 'SimpleLocator'; + private deferred = createDeferred(); constructor( diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts index 19db5d9f34b1..1b2ea8715d35 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts @@ -84,7 +84,7 @@ suite('Conda Env Watcher', async () => { test('Fires when conda `environments.txt` file is created', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const expectedEvent = {}; + const expectedEvent = { providerId: 'conda-envs' }; await setupLocator(async (e) => { deferred.resolve(); actualEvent = e; @@ -99,7 +99,7 @@ suite('Conda Env Watcher', async () => { test('Fires when conda `environments.txt` file is updated', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const expectedEvent = {}; + const expectedEvent = { providerId: 'conda-envs' }; await condaEnvsTxt.create(); await setupLocator(async (e) => { deferred.resolve(); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts index 81ff67884f81..fc1c6927d3fe 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import { getOSType, OSType } from '../../../../../client/common/utils/platform'; import { Disposables } from '../../../../../client/common/utils/resourceLifecycle'; import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; -import { IPythonEnvsIterator } from '../../../../../client/pythonEnvironments/base/locator'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../../../../client/pythonEnvironments/base/locator'; import { FSWatcherKind, FSWatchingLocator, @@ -30,6 +30,8 @@ suite('File System Watching Locator Tests', () => { }); class TestWatcher extends FSWatchingLocator { + public readonly providerId: string = 'test'; + constructor( watcherKind: FSWatcherKind, opts: { @@ -44,7 +46,7 @@ suite('File System Watching Locator Tests', () => { } // eslint-disable-next-line class-methods-use-this - protected doIterEnvs(): IPythonEnvsIterator { + protected doIterEnvs(): IPythonEnvsIterator { throw new Error('Method not implemented.'); } diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts deleted file mode 100644 index 284d5b175dbb..000000000000 --- a/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { IDisposable } from '../../../../../client/common/types'; -import { createDeferred } from '../../../../../client/common/utils/async'; -import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; -import { WatchRootsArgs, WorkspaceLocators } from '../../../../../client/pythonEnvironments/base/locators/wrappers'; -import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; -import { assertSameEnvs, createLocatedEnv, createNamedEnv, getEnvs, SimpleLocator } from '../../common'; - -class WorkspaceFolders { - public added = new EventEmitter(); - - public removed = new EventEmitter(); - - public readonly roots: Uri[]; - - constructor(roots: (Uri | string)[]) { - this.roots = roots.map((r) => (typeof r === 'string' ? Uri.file(r) : r)); - } - - public get onAdded(): Event { - return this.added.event; - } - - public get onRemoved(): Event { - return this.removed.event; - } - - public watchRoots(args: WatchRootsArgs): IDisposable { - const { initRoot, addRoot, removeRoot } = args; - - this.roots.forEach(initRoot); - this.onAdded(addRoot); - this.onRemoved(removeRoot); - - return { - dispose: () => undefined, - }; - } - - public getRootsWatcher(): (args: WatchRootsArgs) => IDisposable { - return (args) => this.watchRoots(args); - } -} - -async function ensureActivated(locators: WorkspaceLocators): Promise { - await getEnvs(locators.iterEnvs()); -} - -suite('WorkspaceLocators', () => { - suite('onChanged', () => { - test('no roots', async () => { - const expected: PythonEnvsChangedEvent[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - - loc1.fire(event1); - - expect(events).to.deep.equal(expected); - }); - - test('no factories', async () => { - const expected: PythonEnvsChangedEvent[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - - loc1.fire(event1); - - expect(events).to.deep.equal(expected); - }); - - test('consolidates events across roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const expected: PythonEnvsChangedEvent[] = [ - { searchLocation: root1, kind: PythonEnvKind.Unknown }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root1 }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root2, kind: PythonEnvKind.Pipenv }, - { searchLocation: root1, kind: PythonEnvKind.Conda }, - ]; - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event3: PythonEnvsChangedEvent = {}; - const event4: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event5: PythonEnvsChangedEvent = { kind: PythonEnvKind.Pipenv }; - const event6: PythonEnvsChangedEvent = { kind: PythonEnvKind.Conda }; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([]); - const loc3 = new SimpleLocator([]); - const loc4 = new SimpleLocator([]); - const loc5 = new SimpleLocator([]); - const loc6 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc2]), - (r) => (r === root1 ? [loc3] : [loc4, loc5]), - (r) => (r === root1 ? [loc6] : []), - ]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - loc1.fire(event1); - loc2.fire(event2); - loc3.fire(event3); - loc4.fire(event4); - loc5.fire(event5); - loc6.fire(event6); - - expect(events).to.deep.equal(expected); - }); - - test('does not identify roots during init', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - // Force r._formatted to be set. - [root1, root2].forEach((r) => r.toString()); - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - await ensureActivated(locators); - - expect(events).to.deep.equal([]); - }); - - test('identifies added roots', async () => { - const added = Uri.file('baz'); - const expected: PythonEnvsChangedEvent[] = [{ searchLocation: added }]; - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - folders.added.fire(added); - - expect(events).to.deep.equal(expected); - }); - - test('identifies removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - // Force r._formatted to be set. - [root1, root2].forEach((r) => r.toString()); - const expected: PythonEnvsChangedEvent[] = [{ searchLocation: root2 }]; - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - folders.removed.fire(root2); - - expect(events).to.deep.equal(expected); - }); - - test('does not emit events from removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const expected: PythonEnvsChangedEvent[] = [ - { searchLocation: root1, kind: PythonEnvKind.Unknown }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root2 }, // removed - { searchLocation: root1 }, - ]; - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event3: PythonEnvsChangedEvent = {}; - const event4: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const loc1 = new SimpleLocator([]); - const loc2 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [(r) => (r === root1 ? [loc1] : [loc2])]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - loc1.fire(event1); - loc2.fire(event2); - folders.removed.fire(root2); - loc1.fire(event3); - loc2.fire(event4); // This one is ignored. - - expect(events).to.deep.equal(expected); - }); - }); - - suite('iterEnvs()', () => { - test('no roots', async () => { - const expected: PythonEnvInfo[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('no factories', async () => { - const expected: PythonEnvInfo[] = []; - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('one empty', async () => { - const root1 = Uri.file('foo'); - const expected: PythonEnvInfo[] = []; - const loc1 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('one not empty', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env1]; - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('empty locator ignored', async () => { - const root1 = Uri.file('foo'); - 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 }); - const loc3 = new SimpleLocator([env2], { before: () => loc2.done }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1, loc2, loc3]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('consolidates envs across roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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 }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query matches a root', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - 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 }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [root1] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query matches all roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - 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 }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [root1, root2] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query does not match a root', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - 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 }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [Uri.file('baz')] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal([]); - }); - - test('query has no searchLocation', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - 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 }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs({ kinds: [PythonEnvKind.Unknown] }); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('iterate out of order', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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 }); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc2.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('iterate intermingled', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo-x', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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 = [ - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - ]; - async function beforeEach(env: PythonEnvInfo) { - const index = expected.indexOf(env); - if (index === 0) { - return; - } - const blockedBy = ordered.indexOf(expected[index - 1]); - await deferreds[blockedBy].promise; - } - async function afterEach(env: PythonEnvInfo) { - const index = ordered.indexOf(env); - deferreds[index].resolve(); - } - const loc1 = new SimpleLocator([env1, env2], { beforeEach, afterEach }); - const loc2 = new SimpleLocator([env3, env4], { beforeEach, afterEach }); - const loc3 = new SimpleLocator([env5, env6], { beforeEach, afterEach }); - const loc4 = new SimpleLocator([env7, env8], { beforeEach, afterEach }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterator = locators.iterEnvs(); - const envs = await getEnvs(iterator); - - assertSameEnvs(envs, expected); - }); - - test('respects roots set during init', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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 }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - - const iterator = locators.iterEnvs(); - const envs = await getEnvs(iterator); - - assertSameEnvs(envs, expected); - }); - - test('respects added roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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 }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iteratorBefore = locators.iterEnvs(); - const envsBefore = await getEnvs(iteratorBefore); - folders.added.fire(root1); - folders.added.fire(root2); - const iteratorAfter = locators.iterEnvs(); - const envsAfter = await getEnvs(iteratorAfter); - - expect(envsBefore).to.deep.equal([]); - expect(envsAfter).to.deep.equal(expected); - }); - - test('ignores removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - 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]); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iteratorBefore = locators.iterEnvs(); - const envsBefore = await getEnvs(iteratorBefore); - folders.removed.fire(root2); - const iteratorAfter = locators.iterEnvs(); - const envsAfter = await getEnvs(iteratorAfter); - - assertSameEnvs(envsBefore, expectedBefore); - assertSameEnvs(envsAfter, expectedAfter); - }); - }); -});