diff --git a/.eslintignore b/.eslintignore index 5bd7c3feb1c2..305fcdc64f3b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -179,10 +179,8 @@ src/test/common/crypto.unit.test.ts src/test/common/configuration/service.unit.test.ts src/test/common/net/fileDownloader.unit.test.ts src/test/common/net/httpClient.unit.test.ts -src/test/common/moduleInstaller.test.ts src/test/common/terminals/activator/index.unit.test.ts src/test/common/terminals/activator/base.unit.test.ts -src/test/common/terminals/activation.conda.unit.test.ts src/test/common/terminals/shellDetector.unit.test.ts src/test/common/terminals/service.unit.test.ts src/test/common/terminals/helper.unit.test.ts @@ -222,7 +220,6 @@ src/test/common/installer/channelManager.unit.test.ts src/test/common/installer/installer.unit.test.ts src/test/common/installer/pipInstaller.unit.test.ts src/test/common/installer/installer.invalidPath.unit.test.ts -src/test/common/installer/moduleInstaller.unit.test.ts src/test/common/installer/pipEnvInstaller.unit.test.ts src/test/common/installer/productPath.unit.test.ts src/test/common/installer/extensionBuildInstaller.unit.test.ts @@ -559,7 +556,6 @@ src/client/common/terminal/shellDetectors/settingsShellDetector.ts src/client/common/terminal/shellDetectors/baseShellDetector.ts src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts -src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts src/client/common/terminal/environmentActivationProviders/commandPrompt.ts src/client/common/terminal/environmentActivationProviders/bash.ts src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts @@ -622,7 +618,6 @@ src/client/common/application/terminalManager.ts src/client/common/application/applicationEnvironment.ts src/client/common/errors/errorUtils.ts src/client/common/installer/serviceRegistry.ts -src/client/common/installer/condaInstaller.ts src/client/common/installer/extensionBuildInstaller.ts src/client/common/installer/productInstaller.ts src/client/common/installer/channelManager.ts diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index 15a66b1d92de..3653bbc18ba0 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -1,10 +1,12 @@ +/* eslint-disable class-methods-use-this */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { ICondaService, ICondaLocatorService } from '../../interpreter/contracts'; +import { ICondaService, ICondaLocatorService, IComponentAdapter } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { ExecutionInfo, IConfigurationService } from '../types'; +import { inDiscoveryExperiment } from '../experiments/helpers'; +import { ExecutionInfo, IConfigurationService, IExperimentService } from '../types'; import { isResource } from '../utils/misc'; import { ModuleInstaller } from './moduleInstaller'; import { InterpreterUri } from './types'; @@ -16,6 +18,9 @@ import { InterpreterUri } from './types'; export class CondaInstaller extends ModuleInstaller { public _isCondaAvailable: boolean | undefined; + // Unfortunately inversify requires the number of args in constructor to be explictly + // specified as more than its base class. So we need the constructor. + // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } @@ -24,7 +29,7 @@ export class CondaInstaller extends ModuleInstaller { return 'Conda'; } - public get displayName() { + public get displayName(): string { return 'Conda'; } @@ -67,7 +72,10 @@ export class CondaInstaller extends ModuleInstaller { const pythonPath = isResource(resource) ? this.serviceContainer.get(IConfigurationService).getSettings(resource).pythonPath : resource.path; - const condaLocatorService = this.serviceContainer.get(ICondaLocatorService); + const experimentService = this.serviceContainer.get(IExperimentService); + const condaLocatorService = (await inDiscoveryExperiment(experimentService)) + ? this.serviceContainer.get(IComponentAdapter) + : this.serviceContainer.get(ICondaLocatorService); const info = await condaLocatorService.getCondaEnvironment(pythonPath); const args = [isUpgrade ? 'update' : 'install']; @@ -97,7 +105,10 @@ export class CondaInstaller extends ModuleInstaller { * Is the provided interprter a conda environment */ private async isCurrentEnvironmentACondaEnvironment(resource?: InterpreterUri): Promise { - const condaService = this.serviceContainer.get(ICondaLocatorService); + const experimentService = this.serviceContainer.get(IExperimentService); + const condaService = (await inDiscoveryExperiment(experimentService)) + ? this.serviceContainer.get(IComponentAdapter) + : this.serviceContainer.get(ICondaLocatorService); const pythonPath = isResource(resource) ? this.serviceContainer.get(IConfigurationService).getSettings(resource).pythonPath : resource.path; diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 9d0c84d66d40..f78a736ed1f5 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -112,9 +112,12 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processServicePromise = processService ? Promise.resolve(processService) : this.processServiceFactory.create(resource); + const condaLocatorService = (await inDiscoveryExperiment(this.experimentService)) + ? this.serviceContainer.get(IComponentAdapter) + : this.serviceContainer.get(ICondaLocatorService); const [condaVersion, condaEnvironment, condaFile, procService] = await Promise.all([ this.condaService.getCondaVersion(), - this.serviceContainer.get(ICondaLocatorService).getCondaEnvironment(pythonPath), + condaLocatorService.getCondaEnvironment(pythonPath), this.condaService.getCondaFile(), processServicePromise, ]); diff --git a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts index 0da1f517a290..eab916a7dd18 100644 --- a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts @@ -1,17 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; + import '../../extensions'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; -import { ICondaLocatorService, ICondaService } from '../../../interpreter/contracts'; +import { IComponentAdapter, ICondaLocatorService, ICondaService } from '../../../interpreter/contracts'; import { IPlatformService } from '../../platform/types'; -import { IConfigurationService } from '../../types'; +import { IConfigurationService, IExperimentService } from '../../types'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; import { IServiceContainer } from '../../../ioc/types'; +import { inDiscoveryExperiment } from '../../experiments/helpers'; // Version number of conda that requires we call activate with 'conda activate' instead of just 'activate' const CondaRequiredMajor = 4; @@ -28,12 +31,15 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman @inject(IPlatformService) private platform: IPlatformService, @inject(IConfigurationService) private configService: IConfigurationService, @inject(IServiceContainer) private serviceContainer: IServiceContainer, + @inject(IExperimentService) private experimentService: IExperimentService, + @inject(IComponentAdapter) private pyenvs: IComponentAdapter, ) {} /** * Is the given shell supported for activating a conda env? */ - public isShellSupported(_targetShell: TerminalShellType): boolean { + // eslint-disable-next-line class-methods-use-this + public isShellSupported(): boolean { return true; } @@ -44,7 +50,7 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman resource: Uri | undefined, targetShell: TerminalShellType, ): Promise { - const pythonPath = this.configService.getSettings(resource).pythonPath; + const { pythonPath } = this.configService.getSettings(resource); return this.getActivationCommandsForInterpreter(pythonPath, targetShell); } @@ -56,10 +62,12 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman pythonPath: string, targetShell: TerminalShellType, ): Promise { - const condaLocatorService = this.serviceContainer.get(ICondaLocatorService); + const condaLocatorService = (await inDiscoveryExperiment(this.experimentService)) + ? this.pyenvs + : this.serviceContainer.get(ICondaLocatorService); const envInfo = await condaLocatorService.getCondaEnvironment(pythonPath); if (!envInfo) { - return; + return undefined; } const condaEnv = envInfo.name.length > 0 ? envInfo.name : envInfo.path; @@ -75,7 +83,7 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman versionInfo.minor >= CondaRequiredMinorForPowerShell && (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) ) { - return this.getPowershellCommands(condaEnv); + return _getPowershellCommands(condaEnv); } if (versionInfo.minor >= CondaRequiredMinor) { // New version. @@ -91,23 +99,22 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman switch (targetShell) { case TerminalShellType.powershell: case TerminalShellType.powershellCore: - return this.getPowershellCommands(condaEnv); + return _getPowershellCommands(condaEnv); // TODO: Do we really special-case fish on Windows? case TerminalShellType.fish: - return this.getFishCommands(condaEnv, await this.condaService.getCondaFile()); + return getFishCommands(condaEnv, await this.condaService.getCondaFile()); default: if (this.platform.isWindows) { return this.getWindowsCommands(condaEnv); - } else { - return this.getUnixCommands(condaEnv, await this.condaService.getCondaFile()); } + return getUnixCommands(condaEnv, await this.condaService.getCondaFile()); } } public async getWindowsActivateCommand(): Promise { - let activateCmd: string = 'activate'; + let activateCmd = 'activate'; const condaExePath = await this.condaService.getCondaFile(); @@ -125,28 +132,25 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman const activate = await this.getWindowsActivateCommand(); return [`${activate} ${condaEnv.toCommandArgument()}`]; } - /** - * The expectation is for the user to configure Powershell for Conda. - * Hence we just send the command `conda activate ...`. - * This configuration is documented on Conda. - * Extension will not attempt to work around issues by trying to setup shell for user. - * - * @param {string} condaEnv - * @returns {(Promise)} - * @memberof CondaActivationCommandProvider - */ - public async getPowershellCommands(condaEnv: string): Promise { - return [`conda activate ${condaEnv.toCommandArgument()}`]; - } +} - public async getFishCommands(condaEnv: string, condaFile: string): Promise { - // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28 - return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`]; - } +/** + * The expectation is for the user to configure Powershell for Conda. + * Hence we just send the command `conda activate ...`. + * This configuration is documented on Conda. + * Extension will not attempt to work around issues by trying to setup shell for user. + */ +export async function _getPowershellCommands(condaEnv: string): Promise { + return [`conda activate ${condaEnv.toCommandArgument()}`]; +} - public async getUnixCommands(condaEnv: string, condaFile: string): Promise { - const condaDir = path.dirname(condaFile); - const activateFile = path.join(condaDir, 'activate'); - return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`]; - } +async function getFishCommands(condaEnv: string, condaFile: string): Promise { + // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28 + return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`]; +} + +async function getUnixCommands(condaEnv: string, condaFile: string): Promise { + const condaDir = path.dirname(condaFile); + const activateFile = path.join(condaDir, 'activate'); + return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`]; } diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index e7690fb88a18..283e4bec8ba1 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -3,15 +3,17 @@ import { inject, injectable, multiInject, named } from 'inversify'; import { Terminal, Uri } from 'vscode'; -import { ICondaLocatorService, IInterpreterService } from '../../interpreter/contracts'; +import { IComponentAdapter, ICondaLocatorService, IInterpreterService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { ITerminalManager } from '../application/types'; +import { inDiscoveryExperiment } from '../experiments/helpers'; import '../extensions'; import { traceDecorators, traceError } from '../logger'; import { IPlatformService } from '../platform/types'; -import { IConfigurationService, Resource } from '../types'; +import { IConfigurationService, IExperimentService, Resource } from '../types'; import { OSType } from '../utils/platform'; import { ShellDetector } from './shellDetector'; import { @@ -28,7 +30,7 @@ export class TerminalHelper implements ITerminalHelper { constructor( @inject(IPlatformService) private readonly platform: IPlatformService, @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, - @inject(ICondaLocatorService) private readonly condaService: ICondaLocatorService, + @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, @inject(IInterpreterService) readonly interpreterService: IInterpreterService, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, @inject(ITerminalActivationCommandProvider) @@ -129,10 +131,14 @@ export class TerminalHelper implements ITerminalHelper { ): Promise { const settings = this.configurationService.getSettings(resource); + const experimentService = this.serviceContainer.get(IExperimentService); + const condaService = (await inDiscoveryExperiment(experimentService)) + ? this.serviceContainer.get(IComponentAdapter) + : this.serviceContainer.get(ICondaLocatorService); // If we have a conda environment, then use that. const isCondaEnvironment = interpreter ? interpreter.envType === EnvironmentType.Conda - : await this.condaService.isCondaEnvironment(settings.pythonPath); + : await condaService.isCondaEnvironment(settings.pythonPath); if (isCondaEnvironment) { const activationCommands = interpreter ? await this.conda.getActivationCommandsForInterpreter(interpreter.path, terminalShellType) diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts index fb5a8244e310..a8d65aef3861 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts @@ -1,13 +1,17 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { parse, SemVer } from 'semver'; -import { traceDecorators, traceWarning } from '../../../../common/logger'; +import { ConfigurationChangeEvent, Uri } from 'vscode'; +import { IWorkspaceService } from '../../../../common/application/types'; +import { inDiscoveryExperiment } from '../../../../common/experiments/helpers'; +import { traceDecorators, traceVerbose, traceWarning } from '../../../../common/logger'; import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; import { IProcessServiceFactory } from '../../../../common/process/types'; +import { IExperimentService, IConfigurationService, IDisposableRegistry } from '../../../../common/types'; import { cache } from '../../../../common/utils/decorators'; import { ICondaService, ICondaLocatorService } from '../../../../interpreter/contracts'; import { IServiceContainer } from '../../../../ioc/types'; -import { CondaInfo } from './conda'; +import { Conda, CondaInfo } from './conda'; /** * A wrapper around a conda installation. @@ -16,18 +20,40 @@ import { CondaInfo } from './conda'; export class CondaService implements ICondaService { private isAvailable: boolean | undefined; + private condaFile: Promise | undefined; + + /** + * Locating conda binary is expensive, since it potentially involves spawning or + * trying to spawn processes; so it's done lazily and asynchronously. Methods that + * need a Conda instance should use getConda() to obtain it, and should never access + * this property directly. + */ + private condaPromise: Promise | undefined; + constructor( @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, @inject(IPlatformService) private platform: IPlatformService, @inject(IFileSystem) private fileSystem: IFileSystem, @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - ) {} + @inject(IExperimentService) private readonly experimentService: IExperimentService, + @inject(IConfigurationService) private readonly configService: IConfigurationService, + @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + ) { + this.addCondaPathChangedHandler(); + } /** * Return the path to the "conda file". */ public async getCondaFile(): Promise { - return this.serviceContainer.get(ICondaLocatorService).getCondaFile(); + if (!(await inDiscoveryExperiment(this.experimentService))) { + return this.serviceContainer.get(ICondaLocatorService).getCondaFile(); + } + if (!this.condaFile) { + this.condaFile = this.getCondaFileImpl(); + } + return this.condaFile; } /** @@ -131,6 +157,46 @@ export class CondaService implements ICondaService { */ @cache(60_000) public async _getCondaInfo(): Promise { - return this.serviceContainer.get(ICondaLocatorService).getCondaInfo(); + if (!(await inDiscoveryExperiment(this.experimentService))) { + return this.serviceContainer.get(ICondaLocatorService).getCondaInfo(); + } + const conda = await this.getConda(); + return conda?.getInfo(); + } + + /** + * Return the path to the "conda file", if there is one (in known locations). + */ + private async getCondaFileImpl(): Promise { + const settings = this.configService.getSettings(); + const setting = settings.condaPath; + if (setting && setting !== '') { + return setting; + } + const conda = await this.getConda(); + return conda?.command ?? 'conda'; + } + + private async getConda(): Promise { + traceVerbose(`Searching for conda.`); + if (this.condaPromise === undefined) { + this.condaPromise = Conda.locate(); + } + return this.condaPromise; + } + + private addCondaPathChangedHandler() { + const disposable = this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); + this.disposableRegistry.push(disposable); + } + + private async onDidChangeConfiguration(event: ConfigurationChangeEvent) { + const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders + ? this.workspaceService.workspaceFolders!.map((workspace) => workspace.uri) + : [undefined]; + if (workspacesUris.findIndex((uri) => event.affectsConfiguration('python.condaPath', uri)) === -1) { + return; + } + this.condaFile = undefined; } } diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index bcd5fc5ca73b..31a3e8a20b76 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -451,9 +451,8 @@ export async function registerLegacyDiscoveryForIOC(serviceManager: IServiceMana PIPENV_SERVICE, ); serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper); + serviceManager.addSingleton(ICondaLocatorService, CondaLocatorService); } - - serviceManager.addSingleton(ICondaLocatorService, CondaLocatorService); serviceManager.addSingleton(ICondaService, CondaService); } diff --git a/src/test/common/installer/condaInstaller.unit.test.ts b/src/test/common/installer/condaInstaller.unit.test.ts index 12f38909029d..3fe9c83dbdae 100644 --- a/src/test/common/installer/condaInstaller.unit.test.ts +++ b/src/test/common/installer/condaInstaller.unit.test.ts @@ -8,9 +8,15 @@ import { instance, mock, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; +import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; import { InterpreterUri } from '../../../client/common/installer/types'; -import { ExecutionInfo, IConfigurationService, IPythonSettings } from '../../../client/common/types'; +import { + ExecutionInfo, + IConfigurationService, + IExperimentService, + IPythonSettings, +} from '../../../client/common/types'; import { ICondaService, ICondaLocatorService } from '../../../client/interpreter/contracts'; import { ServiceContainer } from '../../../client/ioc/container'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -23,6 +29,7 @@ suite('Common - Conda Installer', () => { let condaService: ICondaService; let condaLocatorService: ICondaLocatorService; let configService: IConfigurationService; + let experimentService: IExperimentService; class CondaInstallerTest extends CondaInstaller { public async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { return super.getExecutionInfo(moduleName, resource); @@ -31,13 +38,16 @@ suite('Common - Conda Installer', () => { setup(() => { serviceContainer = mock(ServiceContainer); condaService = mock(CondaService); + experimentService = mock(); condaLocatorService = mock(); + when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(false); configService = mock(ConfigurationService); when(serviceContainer.get(ICondaService)).thenReturn(instance(condaService)); when(serviceContainer.get(ICondaLocatorService)).thenReturn( instance(condaLocatorService), ); when(serviceContainer.get(IConfigurationService)).thenReturn(instance(configService)); + when(serviceContainer.get(IExperimentService)).thenReturn(instance(experimentService)); installer = new CondaInstallerTest(instance(serviceContainer)); }); test('Name and priority', async () => { diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 2ec98fb636e1..df1d49e1e5a8 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -20,18 +21,20 @@ import { } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../../../client/common/constants'; +import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; import { ModuleInstaller } from '../../../client/common/installer/moduleInstaller'; import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; import { PipInstaller } from '../../../client/common/installer/pipInstaller'; import { ProductInstaller } from '../../../client/common/installer/productInstaller'; -import { IInstallationChannelManager, IModuleInstaller, InterpreterUri } from '../../../client/common/installer/types'; +import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; import { IFileSystem } from '../../../client/common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { ExecutionInfo, IConfigurationService, IDisposableRegistry, + IExperimentService, IOutputChannel, IPythonSettings, ModuleNamePurpose, @@ -65,16 +68,20 @@ suite('Module Installer', () => { public get priority(): number { return 0; } + public get name(): string { return ''; } + public get displayName(): string { return ''; } - public isSupported(_resource?: InterpreterUri): Promise { + + public isSupported(): Promise { return Promise.resolve(false); } - public getExecutionInfo(_moduleName: string, _resource?: InterpreterUri): Promise { + + public getExecutionInfo(): Promise { return Promise.resolve({ moduleName: 'executionInfo', args: [] }); } @@ -109,7 +116,9 @@ suite('Module Installer', () => { test('Show error message if sudo exec fails with error', async () => { const error = 'Error message'; const sudoPromptMock = { - exec: (_command: any, _options: any, callBackFn: Function) => callBackFn(error, 'stdout', 'stderr'), + // eslint-disable-next-line @typescript-eslint/ban-types + exec: (_command: unknown, _options: unknown, callBackFn: Function) => + callBackFn(error, 'stdout', 'stderr'), }; rewiremock.enable(); rewiremock('sudo-prompt').with(sudoPromptMock); @@ -130,7 +139,9 @@ suite('Module Installer', () => { test('Show stdout if sudo exec succeeds', async () => { const stdout = 'stdout'; const sudoPromptMock = { - exec: (_command: any, _options: any, callBackFn: Function) => callBackFn(undefined, stdout, undefined), + // eslint-disable-next-line @typescript-eslint/ban-types + exec: (_command: unknown, _options: unknown, callBackFn: Function) => + callBackFn(undefined, stdout, undefined), }; rewiremock.enable(); rewiremock('sudo-prompt').with(sudoPromptMock); @@ -154,7 +165,9 @@ suite('Module Installer', () => { test('Show stderr if sudo exec gives a warning with stderr', async () => { const stderr = 'stderr'; const sudoPromptMock = { - exec: (_command: any, _options: any, callBackFn: Function) => callBackFn(undefined, undefined, stderr), + // eslint-disable-next-line @typescript-eslint/ban-types + exec: (_command: unknown, _options: unknown, callBackFn: Function) => + callBackFn(undefined, undefined, stderr), }; rewiremock.enable(); rewiremock('sudo-prompt').with(sudoPromptMock); @@ -177,14 +190,14 @@ suite('Module Installer', () => { }); }); - [CondaInstaller, PipInstaller, PipEnvInstaller, TestModuleInstaller].forEach((installerClass) => { + [CondaInstaller, PipInstaller, PipEnvInstaller, TestModuleInstaller].forEach((InstallerClass) => { // Proxy info is relevant only for PipInstaller. - const proxyServers = installerClass === PipInstaller ? ['', 'proxy:1234'] : ['']; + const proxyServers = InstallerClass === PipInstaller ? ['', 'proxy:1234'] : ['']; proxyServers.forEach((proxyServer) => { [undefined, Uri.file('/users/dev/xyz')].forEach((resource) => { // Conda info is relevant only for CondaInstaller. const condaEnvs = - installerClass === CondaInstaller + InstallerClass === CondaInstaller ? [ { name: 'My-Env01', path: '' }, { name: '', path: path.join('conda', 'path') }, @@ -194,19 +207,21 @@ suite('Module Installer', () => { : []; [undefined, ...condaEnvs].forEach((condaEnvInfo) => { const testProxySuffix = proxyServer.length === 0 ? 'without proxy info' : 'with proxy info'; + // eslint-disable-next-line no-nested-ternary const testCondaEnv = condaEnvInfo ? condaEnvInfo.name ? 'without conda name' : 'with conda path' : 'without conda'; const testSuite = [testProxySuffix, testCondaEnv].filter((item) => item.length > 0).join(', '); - suite(`${installerClass.name} (${testSuite})`, () => { + suite(`${InstallerClass.name} (${testSuite})`, () => { let disposables: Disposable[] = []; let installationChannel: TypeMoq.IMock; let terminalService: TypeMoq.IMock; let configService: TypeMoq.IMock; let fs: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; + let experimentService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let installer: IModuleInstaller; const condaExecutable = 'my.exe'; @@ -223,6 +238,17 @@ suite('Module Installer', () => { .setup((c) => c.get(TypeMoq.It.isValue(IFileSystem))) .returns(() => fs.object); + experimentService = TypeMoq.Mock.ofType(); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) + .returns(() => Promise.resolve(false)); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoveryWithoutFileWatching)) + .returns(() => Promise.resolve(false)); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) + .returns(() => experimentService.object); + disposables = []; serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())) @@ -285,7 +311,7 @@ suite('Module Installer', () => { .setup((w) => w.getConfiguration(TypeMoq.It.isValue('http'))) .returns(() => http.object); - installer = new installerClass(serviceContainer.object); + installer = new InstallerClass(serviceContainer.object); }); teardown(() => { disposables.forEach((disposable) => { @@ -302,7 +328,7 @@ suite('Module Installer', () => { .verifiable(TypeMoq.Times.atLeastOnce()); } getModuleNamesForTesting().forEach((product) => { - const moduleName = product.moduleName; + const { moduleName } = product; async function installModuleAndVerifyCommand( command: string, expectedArgs: string[], @@ -330,7 +356,7 @@ suite('Module Installer', () => { const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${ interpreterInfo.version ? interpreterInfo.version.raw : '' }`; - if (installerClass === PipInstaller) { + if (InstallerClass === PipInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const proxyArgs = @@ -346,14 +372,14 @@ suite('Module Installer', () => { await installModuleAndVerifyCommand(pythonPath, expectedArgs); }); } - if (installerClass === PipEnvInstaller) { + if (InstallerClass === PipEnvInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const expectedArgs = ['install', '"pylint<2.0.0"', '--dev']; await installModuleAndVerifyCommand(pipenvName, expectedArgs); }); } - if (installerClass === CondaInstaller) { + if (InstallerClass === CondaInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const expectedArgs = ['install']; @@ -373,7 +399,7 @@ suite('Module Installer', () => { const testTitle = `Ensure install arg is \'pylint\' in ${ interpreterInfo.version ? interpreterInfo.version.raw : '' }`; - if (installerClass === PipInstaller) { + if (InstallerClass === PipInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const proxyArgs = @@ -389,14 +415,14 @@ suite('Module Installer', () => { await installModuleAndVerifyCommand(pythonPath, expectedArgs); }); } - if (installerClass === PipEnvInstaller) { + if (InstallerClass === PipEnvInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const expectedArgs = ['install', 'pylint', '--dev']; await installModuleAndVerifyCommand(pipenvName, expectedArgs); }); } - if (installerClass === CondaInstaller) { + if (InstallerClass === CondaInstaller) { test(testTitle, async () => { setActiveInterpreter(interpreterInfo); const expectedArgs = ['install']; @@ -417,10 +443,11 @@ suite('Module Installer', () => { return; } - if (installerClass === TestModuleInstaller) { + if (InstallerClass === TestModuleInstaller) { suite(`If interpreter type is Unknown (${product.name})`, async () => { test(`If 'python.globalModuleInstallation' is set to true and pythonPath directory is read only, do an elevated install`, async () => { const info = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any info.setup((t: any) => t.then).returns(() => undefined); info.setup((t) => t.envType).returns(() => EnvironmentType.Unknown); info.setup((t) => t.version).returns(() => new SemVer('3.5.0-final')); @@ -445,6 +472,7 @@ suite('Module Installer', () => { }); test(`If 'python.globalModuleInstallation' is set to true and pythonPath directory is not read only, send command to terminal`, async () => { const info = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any info.setup((t: any) => t.then).returns(() => undefined); info.setup((t) => t.envType).returns(() => EnvironmentType.Unknown); info.setup((t) => t.version).returns(() => new SemVer('3.5.0-final')); @@ -468,6 +496,7 @@ suite('Module Installer', () => { }); test(`If 'python.globalModuleInstallation' is not set to true, concatenate arguments with '--user' flag and send command to terminal`, async () => { const info = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any info.setup((t: any) => t.then).returns(() => undefined); info.setup((t) => t.envType).returns(() => EnvironmentType.Unknown); info.setup((t) => t.version).returns(() => new SemVer('3.5.0-final')); @@ -488,6 +517,7 @@ suite('Module Installer', () => { }); test(`ignores failures in IFileSystem.isDirReadonly()`, async () => { const info = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any info.setup((t: any) => t.then).returns(() => undefined); info.setup((t) => t.envType).returns(() => EnvironmentType.Unknown); info.setup((t) => t.version).returns(() => new SemVer('3.5.0-final')); @@ -520,7 +550,7 @@ suite('Module Installer', () => { }; appShell .setup((a) => a.withProgress(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .callback((expected, _) => assert.deepEqual(expected, options)) + .callback((expected) => assert.deepEqual(expected, options)) .returns(() => Promise.resolve()) .verifiable(TypeMoq.Times.once()); try { @@ -538,7 +568,7 @@ suite('Module Installer', () => { }); } - if (installerClass === PipInstaller) { + if (InstallerClass === PipInstaller) { test(`Ensure getActiveInterpreter is used in PipInstaller (${product.name})`, async () => { setActiveInterpreter(); try { @@ -549,7 +579,7 @@ suite('Module Installer', () => { interpreterService.verifyAll(); }); } - if (installerClass === PipInstaller) { + if (InstallerClass === PipInstaller) { test(`Test Args (${product.name})`, async () => { setActiveInterpreter(); const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; @@ -558,7 +588,7 @@ suite('Module Installer', () => { interpreterService.verifyAll(); }); } - if (installerClass === PipEnvInstaller) { + if (InstallerClass === PipEnvInstaller) { [false, true].forEach((isUpgrade) => { test(`Test args (${product.name})`, async () => { setActiveInterpreter(); @@ -570,7 +600,7 @@ suite('Module Installer', () => { }); }); } - if (installerClass === CondaInstaller) { + if (InstallerClass === CondaInstaller) { [false, true].forEach((isUpgrade) => { test(`Test args (${product.name})`, async () => { setActiveInterpreter(); @@ -605,6 +635,7 @@ function generatePythonInterpreterVersions() { ); return versions.map((version) => { const info = TypeMoq.Mock.ofType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any info.setup((t: any) => t.then).returns(() => undefined); info.setup((t) => t.envType).returns(() => EnvironmentType.VirtualEnv); info.setup((t) => t.version).returns(() => version); @@ -623,7 +654,7 @@ function getModuleNamesForTesting(): { name: string; value: Product; moduleName: moduleName = prodInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.install); return { name: product.name, value: product.value, moduleName }; } catch { - return; + return undefined; } }) .filter((item) => item !== undefined) as { name: string; value: Product; moduleName: string }[]; diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 22cd39672330..5f02f5264310 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -1,4 +1,4 @@ -import { expect, should as chai_should, use as chai_use } from 'chai'; +import { expect, should as chaiShould, use as chaiUse } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as path from 'path'; import { SemVer } from 'semver'; @@ -34,6 +34,7 @@ import { AsyncDisposableRegistry } from '../../client/common/asyncDisposableRegi import { ConfigurationService } from '../../client/common/configuration/service'; import { CryptoUtils } from '../../client/common/crypto'; import { EditorUtils } from '../../client/common/editor'; +import { DiscoveryVariants } from '../../client/common/experiments/groups'; import { ExperimentsManager } from '../../client/common/experiments/manager'; import { ExperimentService } from '../../client/common/experiments/service'; import { FeatureDeprecationManager } from '../../client/common/featureDeprecationManager'; @@ -134,9 +135,9 @@ import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; -import { closeActiveWindows, initializeTest } from './../initialize'; +import { closeActiveWindows, initializeTest } from '../initialize'; -chai_use(chaiAsPromised); +chaiUse(chaiAsPromised); const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); @@ -158,13 +159,14 @@ suite('Module Installer', () => { let mockTerminalService: TypeMoq.IMock; let condaService: TypeMoq.IMock; let condaLocatorService: TypeMoq.IMock; + let experimentService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let mockTerminalFactory: TypeMoq.IMock; const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); suiteSetup(initializeTest); setup(async () => { - chai_should(); + chaiShould(); await initializeDI(); await initializeTest(); await resetSettings(); @@ -219,6 +221,10 @@ suite('Module Installer', () => { await ioc.registerMockInterpreterTypes(); condaService = TypeMoq.Mock.ofType(); condaLocatorService = TypeMoq.Mock.ofType(); + experimentService = TypeMoq.Mock.ofType(); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) + .returns(() => Promise.resolve(false)); ioc.serviceManager.addSingletonInstance( ICondaLocatorService, condaLocatorService.object, @@ -340,15 +346,14 @@ suite('Module Installer', () => { ); } async function getCurrentPythonPath(): Promise { - const pythonPath = getExtensionSettings(workspaceUri).pythonPath; + const { pythonPath } = getExtensionSettings(workspaceUri); if (path.basename(pythonPath) === pythonPath) { const pythonProc = await ioc.serviceContainer .get(IPythonExecutionFactory) .create({ resource: workspaceUri }); return pythonProc.getExecutablePath().catch(() => pythonPath); - } else { - return pythonPath; } + return pythonPath; } test('Ensure pip is supported and conda is not', async () => { ioc.serviceManager.addSingletonInstance( @@ -466,6 +471,9 @@ suite('Module Installer', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) .returns(() => condaLocatorService.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) + .returns(() => experimentService.object); condaService.setup((c) => c.isCondaAvailable()).returns(() => Promise.resolve(true)); condaLocatorService .setup((c) => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))) @@ -489,6 +497,9 @@ suite('Module Installer', () => { serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) .returns(() => condaLocatorService.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) + .returns(() => experimentService.object); condaService.setup((c) => c.isCondaAvailable()).returns(() => Promise.resolve(true)); condaLocatorService .setup((c) => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))) @@ -537,11 +548,12 @@ suite('Module Installer', () => { .setup((t) => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns((_cmd: string, args: string[]) => { argsSent = args; - return Promise.resolve(void 0); + return Promise.resolve(); }); interpreterService .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) + // eslint-disable-next-line @typescript-eslint/no-explicit-any .returns(() => Promise.resolve({ envType: EnvironmentType.Unknown } as any)); await pipInstaller.installModule(moduleName, resource); @@ -582,7 +594,7 @@ suite('Module Installer', () => { .setup((t) => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns((_cmd: string, args: string[]) => { argsSent = args; - return Promise.resolve(void 0); + return Promise.resolve(); }); await pipInstaller.installModule(moduleName, resource); @@ -620,7 +632,7 @@ suite('Module Installer', () => { .returns((cmd: string, args: string[]) => { argsSent = args; command = cmd; - return Promise.resolve(void 0); + return Promise.resolve(); }); await pipInstaller.installModule(moduleName, resource); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 21a582e10f8b..a2b559faaaac 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -41,6 +41,7 @@ import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnviro import * as ExperimentHelpers from '../../../client/common/experiments/helpers'; import * as WindowsStoreInterpreter from '../../../client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { ExperimentService } from '../../../client/common/experiments/service'; +import { DiscoveryVariants } from '../../../client/common/experiments/groups'; const pythonInterpreter: PythonEnvironment = { path: '/foo/bar/python.exe', @@ -105,6 +106,8 @@ suite('Process - PythonExecutionFactory', () => { condaLocatorService = mock(); processLogger = mock(ProcessLogger); experimentService = mock(ExperimentService); + when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(false); + when(experimentService.inExperiment(DiscoveryVariants.discoveryWithoutFileWatching)).thenResolve(false); pyenvs = mock(); when(pyenvs.isWindowsStoreInterpreter(anyString())).thenResolve(true); diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index cdbe4b8e31aa..bffa8a92f630 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -8,12 +8,16 @@ import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { Disposable } from 'vscode'; import { TerminalManager } from '../../../client/common/application/terminalManager'; +import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import '../../../client/common/extensions'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../../client/common/terminal/environmentActivationProviders/commandPrompt'; -import { CondaActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; +import { + CondaActivationCommandProvider, + _getPowershellCommands, +} from '../../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider'; import { PyEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; import { TerminalHelper } from '../../../client/common/terminal/helper'; @@ -21,11 +25,12 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../../../ import { IConfigurationService, IDisposableRegistry, + IExperimentService, IPythonSettings, ITerminalSettings, } from '../../../client/common/types'; import { getNamesAndValues } from '../../../client/common/utils/enum'; -import { ICondaLocatorService, ICondaService } from '../../../client/interpreter/contracts'; +import { IComponentAdapter, ICondaLocatorService, ICondaService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -41,6 +46,8 @@ suite('Terminal Environment Activation conda', () => { let procServiceFactory: TypeMoq.IMock; let condaService: TypeMoq.IMock; let condaLocatorService: TypeMoq.IMock; + let experimentService: TypeMoq.IMock; + let componentAdapter: TypeMoq.IMock; let configService: TypeMoq.IMock; let conda: string; let bash: ITerminalActivationCommandProvider; @@ -53,14 +60,26 @@ suite('Terminal Environment Activation conda', () => { .setup((c) => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())) .returns(() => disposables); + experimentService = TypeMoq.Mock.ofType(); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) + .returns(() => Promise.resolve(false)); + componentAdapter = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); processService = TypeMoq.Mock.ofType(); condaLocatorService = TypeMoq.Mock.ofType(); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) + .returns(() => condaLocatorService.object); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IExperimentService))) + .returns(() => experimentService.object); condaService = TypeMoq.Mock.ofType(); condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve(conda)); bash = mock(Bash); + // eslint-disable-next-line @typescript-eslint/no-explicit-any processService.setup((x: any) => x.then).returns(() => undefined); procServiceFactory = TypeMoq.Mock.ofType(); procServiceFactory @@ -80,7 +99,7 @@ suite('Terminal Environment Activation conda', () => { .setup((c) => c.get(TypeMoq.It.isValue(ICondaService), TypeMoq.It.isAny())) .returns(() => condaService.object); serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService), TypeMoq.It.isAny())) + .setup((c) => c.get(TypeMoq.It.isValue(ICondaLocatorService))) .returns(() => condaLocatorService.object); configService = TypeMoq.Mock.ofType(); @@ -96,7 +115,7 @@ suite('Terminal Environment Activation conda', () => { terminalHelper = new TerminalHelper( platformService.object, instance(mock(TerminalManager)), - condaLocatorService.object, + serviceContainer.object, instance(mock(InterpreterService)), configService.object, new CondaActivationCommandProvider( @@ -104,6 +123,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ), instance(bash), mock(CommandPromptAndPowerShell), @@ -135,6 +156,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ); const activationCommands = await provider.getActivationCommands(undefined, TerminalShellType.fish); @@ -164,6 +187,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ); const activationCommands = await provider.getActivationCommands(undefined, TerminalShellType.bash); @@ -193,6 +218,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ); const activationCommands = await provider.getActivationCommands(undefined, TerminalShellType.bash); @@ -270,6 +297,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ); const activationCommands = await provider.getActivationCommands(undefined, TerminalShellType.bash); @@ -299,6 +328,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ).getActivationCommands(undefined, shellType); let expectedActivationCommand: string[] | undefined; const expectEnvActivatePath = path.dirname(pythonPath); @@ -640,6 +671,8 @@ suite('Terminal Environment Activation conda', () => { platformService.object, configService.object, serviceContainer.object, + experimentService.object, + componentAdapter.object, ); let result: string[] | undefined; @@ -647,7 +680,7 @@ suite('Terminal Environment Activation conda', () => { if (testParams.terminalKind === TerminalShellType.commandPrompt) { result = await tstCmdProvider.getWindowsCommands(testParams.envName); } else { - result = await tstCmdProvider.getPowershellCommands(testParams.envName); + result = await _getPowershellCommands(testParams.envName); } expect(result).to.deep.equal(testParams.expectedResult, 'Specific terminal command is incorrect.'); }); diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index 95dff2832e22..e2997b28bc44 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -9,6 +9,7 @@ import { TerminalManager } from '../../../client/common/application/terminalMana import { ITerminalManager } from '../../../client/common/application/types'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; +import { DiscoveryVariants } from '../../../client/common/experiments/groups'; import { PlatformService } from '../../../client/common/platform/platformService'; import { IPlatformService } from '../../../client/common/platform/types'; import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash'; @@ -24,11 +25,12 @@ import { ITerminalActivationCommandProvider, TerminalShellType, } from '../../../client/common/terminal/types'; -import { IConfigurationService } from '../../../client/common/types'; +import { IConfigurationService, IExperimentService } from '../../../client/common/types'; import { getNamesAndValues } from '../../../client/common/utils/enum'; import { Architecture, OSType } from '../../../client/common/utils/platform'; import { ICondaLocatorService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; +import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; suite('Terminal Service helpers', () => { @@ -36,6 +38,8 @@ suite('Terminal Service helpers', () => { let terminalManager: ITerminalManager; let platformService: IPlatformService; let condaService: ICondaLocatorService; + let experimentService: IExperimentService; + let serviceContainer: IServiceContainer; let configurationService: IConfigurationService; let condaActivationProvider: ITerminalActivationCommandProvider; let bashActivationProvider: ITerminalActivationCommandProvider; @@ -58,7 +62,12 @@ suite('Terminal Service helpers', () => { mockDetector = mock(TerminalNameShellDetector); terminalManager = mock(TerminalManager); platformService = mock(PlatformService); + experimentService = mock(); + when(experimentService.inExperiment(DiscoveryVariants.discoverWithFileWatching)).thenResolve(false); + serviceContainer = mock(); condaService = mock(); + when(serviceContainer.get(IExperimentService)).thenReturn(instance(experimentService)); + when(serviceContainer.get(ICondaLocatorService)).thenReturn(instance(condaService)); configurationService = mock(ConfigurationService); condaActivationProvider = mock(CondaActivationCommandProvider); bashActivationProvider = mock(Bash); @@ -70,7 +79,7 @@ suite('Terminal Service helpers', () => { helper = new TerminalHelper( instance(platformService), instance(terminalManager), - instance(condaService), + instance(serviceContainer), instance(mock(InterpreterService)), instance(configurationService), instance(condaActivationProvider), diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index a4ea1b5a6802..ca45bbb6613f 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -41,6 +41,7 @@ import { ILintMessage, LinterId, LintMessageSeverity } from '../../client/linter import { deleteFile, PYTHON_PATH } from '../common'; import { BaseTestFixture, getLinterID, getProductName, newMockDocument, throwUnknownProduct } from './common'; import * as ExperimentHelpers from '../../client/common/experiments/helpers'; +import { DiscoveryVariants } from '../../client/common/experiments/groups'; const workspaceDir = path.join(__dirname, '..', '..', '..', 'src', 'test'); const workspaceUri = Uri.file(workspaceDir); @@ -727,6 +728,9 @@ class TestFixture extends BaseTestFixture { const pyenvs: IComponentAdapter = mock(); const experimentService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) + .returns(() => Promise.resolve(false)); const inDiscoveryExperimentStub = sinon.stub(ExperimentHelpers, 'inDiscoveryExperiment'); inDiscoveryExperimentStub.resolves(false); diff --git a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts index 784194c60138..c06a69eb65ff 100644 --- a/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts +++ b/src/test/pythonEnvironments/discovery/locators/condaService.unit.test.ts @@ -2,6 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { parse } from 'semver'; import * as TypeMoq from 'typemoq'; +import { IWorkspaceService } from '../../../../client/common/application/types'; import { DiscoveryVariants } from '../../../../client/common/experiments/groups'; import { FileSystemPaths, FileSystemPathUtils } from '../../../../client/common/platform/fs-paths'; @@ -22,9 +23,11 @@ suite('Interpreters Conda Service', () => { let procServiceFactory: TypeMoq.IMock; let condaPathSetting: string; let experimentService: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; setup(async () => { condaPathSetting = ''; processService = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); config = TypeMoq.Mock.ofType(); @@ -73,6 +76,10 @@ suite('Interpreters Conda Service', () => { platformService.object, fileSystem.object, serviceContainer.object, + experimentService.object, + config.object, + [], + workspaceService.object, ); }); diff --git a/src/test/refactor/rename.test.ts b/src/test/refactor/rename.test.ts index d0890458d9c0..ebd74f965c9c 100644 --- a/src/test/refactor/rename.test.ts +++ b/src/test/refactor/rename.test.ts @@ -18,6 +18,7 @@ import { workspace, } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { DiscoveryVariants } from '../../client/common/experiments/groups'; import '../../client/common/extensions'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; @@ -91,6 +92,10 @@ suite('Refactor Rename', () => { const pyenvs: IComponentAdapter = mock(); + experimentService + .setup((e) => e.inExperiment(DiscoveryVariants.discoverWithFileWatching)) + .returns(() => Promise.resolve(false)); + serviceContainer .setup((s) => s.get(typeMoq.It.isValue(IPythonExecutionFactory), typeMoq.It.isAny())) .returns(