diff --git a/src/client/common/process/pythonEnvironment.ts b/src/client/common/process/pythonEnvironment.ts index 14828b9fd120..90465c68ee0c 100644 --- a/src/client/common/process/pythonEnvironment.ts +++ b/src/client/common/process/pythonEnvironment.ts @@ -2,16 +2,16 @@ // Licensed under the MIT License. import { traceError, traceInfo } from '../../logging'; -import { CondaEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/conda'; +import { Conda, CondaEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/conda'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation } from '../../pythonEnvironments/info'; import { getExecutablePath } from '../../pythonEnvironments/info/executable'; import { getInterpreterInfo } from '../../pythonEnvironments/info/interpreter'; import { IFileSystem } from '../platform/types'; import * as internalPython from './internal/python'; -import { ExecutionResult, IProcessService, ShellOptions, SpawnOptions } from './types'; +import { ExecutionResult, IProcessService, IPythonEnvironment, ShellOptions, SpawnOptions } from './types'; -class PythonEnvironment { +class PythonEnvironment implements IPythonEnvironment { private cachedExecutablePath: Map> = new Map>(); private cachedInterpreterInformation: InterpreterInformation | undefined | null = null; @@ -28,13 +28,13 @@ class PythonEnvironment { }, ) {} - public getExecutionInfo(pythonArgs: string[] = []): PythonExecInfo { + public getExecutionInfo(pythonArgs: string[] = [], pythonExecutable?: string): PythonExecInfo { const python = this.deps.getPythonArgv(this.pythonPath); - return buildPythonExecInfo(python, pythonArgs); + return buildPythonExecInfo(python, pythonArgs, pythonExecutable); } - public getExecutionObservableInfo(pythonArgs: string[] = []): PythonExecInfo { + public getExecutionObservableInfo(pythonArgs: string[] = [], pythonExecutable?: string): PythonExecInfo { const python = this.deps.getObservablePythonArgv(this.pythonPath); - return buildPythonExecInfo(python, pythonArgs); + return buildPythonExecInfo(python, pythonArgs, pythonExecutable); } public async getInterpreterInformation(): Promise { @@ -131,21 +131,18 @@ export function createPythonEnv( return new PythonEnvironment(pythonPath, deps); } -export function createCondaEnv( - condaFile: string, +export async function createCondaEnv( condaInfo: CondaEnvironmentInfo, pythonPath: string, // These are used to generate the deps. procs: IProcessService, fs: IFileSystem, -): PythonEnvironment { - const runArgs = ['run']; - if (condaInfo.name === '') { - runArgs.push('-p', condaInfo.path); - } else { - runArgs.push('-n', condaInfo.name); +): Promise { + const conda = await Conda.getConda(); + const pythonArgv = await conda?.getRunPythonArgs({ name: condaInfo.name, prefix: condaInfo.path }); + if (!pythonArgv) { + return undefined; } - const pythonArgv = [condaFile, ...runArgs, '--no-capture-output', 'python']; const deps = createDeps( async (filename) => fs.pathExists(filename), pythonArgv, diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 5d88a776faf7..e20a6392fc05 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -1,13 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { gte } from 'semver'; -import { Uri } from 'vscode'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; -import { IComponentAdapter, ICondaService } from '../../interpreter/contracts'; +import { IComponentAdapter } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { CondaEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/conda'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IFileSystem } from '../platform/types'; @@ -22,6 +19,7 @@ import { IProcessLogger, IProcessService, IProcessServiceFactory, + IPythonEnvironment, IPythonExecutionFactory, IPythonExecutionService, } from './types'; @@ -29,9 +27,6 @@ import { IInterpreterAutoSelectionService } from '../../interpreter/autoSelectio import { sleep } from '../utils/async'; import { traceError } from '../../logging'; -// Minimum version number of conda required to be able to use 'conda run' and '--no-capture--output' option -export const CONDA_RUN_VERSION = '4.9.0'; - @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { private readonly disposables: IDisposableRegistry; @@ -45,7 +40,6 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(ICondaService) private readonly condaService: ICondaService, @inject(IBufferDecoder) private readonly decoder: IBufferDecoder, @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, @inject(IInterpreterAutoSelectionService) private readonly autoSelection: IInterpreterAutoSelectionService, @@ -90,13 +84,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const windowsStoreInterpreterCheck = this.pyenvs.isWindowsStoreInterpreter.bind(this.pyenvs); - return createPythonService( - pythonPath, - processService, - this.fileSystem, - undefined, - await windowsStoreInterpreterCheck(pythonPath), - ); + const env = (await windowsStoreInterpreterCheck(pythonPath)) + ? createWindowsStoreEnv(pythonPath, processService) + : createPythonEnv(pythonPath, processService, this.fileSystem); + + return createPythonService(processService, env); } public async createActivatedEnvironment( @@ -126,60 +118,28 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { if (condaExecutionService) { return condaExecutionService; } - - return createPythonService(pythonPath, processService, this.fileSystem); + const env = createPythonEnv(pythonPath, processService, this.fileSystem); + return createPythonService(processService, env); } public async createCondaExecutionService( pythonPath: string, - processService?: IProcessService, - resource?: Uri, + processService: IProcessService, ): Promise { - const processServicePromise = processService - ? Promise.resolve(processService) - : this.processServiceFactory.create(resource); const condaLocatorService = this.serviceContainer.get(IComponentAdapter); - const [condaVersion, condaEnvironment, condaFile, procService] = await Promise.all([ - this.condaService.getCondaVersion(), - condaLocatorService.getCondaEnvironment(pythonPath), - this.condaService.getCondaFile(), - processServicePromise, - ]); - - if (condaVersion && gte(condaVersion, CONDA_RUN_VERSION) && condaEnvironment && condaFile && procService) { - // Add logging to the newly created process service - if (!processService) { - procService.on('exec', this.logger.logProcess.bind(this.logger)); - this.disposables.push(procService); - } - - return createPythonService( - pythonPath, - procService, - this.fileSystem, - // This is what causes a CondaEnvironment to be returned: - [condaFile, condaEnvironment], - ); + const [condaEnvironment] = await Promise.all([condaLocatorService.getCondaEnvironment(pythonPath)]); + if (!condaEnvironment) { + return undefined; } - - return Promise.resolve(undefined); + const env = await createCondaEnv(condaEnvironment, pythonPath, processService, this.fileSystem); + if (!env) { + return undefined; + } + return createPythonService(processService, env); } } -function createPythonService( - pythonPath: string, - procService: IProcessService, - fs: IFileSystem, - conda?: [string, CondaEnvironmentInfo], - isWindowsStore?: boolean, -): IPythonExecutionService { - let env = createPythonEnv(pythonPath, procService, fs); - if (conda) { - const [condaPath, condaInfo] = conda; - env = createCondaEnv(condaPath, condaInfo, pythonPath, procService, fs); - } else if (isWindowsStore) { - env = createWindowsStoreEnv(pythonPath, procService); - } +function createPythonService(procService: IProcessService, env: IPythonEnvironment): IPythonExecutionService { const procs = createPythonProcessService(procService, env); return { getInterpreterInformation: () => env.getInterpreterInformation(), diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 6536019e10f7..28a11bdb5718 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -5,7 +5,7 @@ import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { ErrorUtils } from '../errors/errorUtils'; import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError'; import * as internalPython from './internal/python'; -import { ExecutionResult, IProcessService, ObservableExecutionResult, SpawnOptions } from './types'; +import { ExecutionResult, IProcessService, IPythonEnvironment, ObservableExecutionResult, SpawnOptions } from './types'; class PythonProcessService { constructor( @@ -69,11 +69,7 @@ class PythonProcessService { export function createPythonProcessService( procs: IProcessService, // from PythonEnvironment: - env: { - getExecutionInfo(pythonArgs?: string[]): PythonExecInfo; - getExecutionObservableInfo(pythonArgs?: string[]): PythonExecInfo; - isModuleInstalled(moduleName: string): Promise; - }, + env: IPythonEnvironment, ) { const deps = { // from PythonService: diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 1defb4901e48..5134b63e8425 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -82,8 +82,7 @@ export interface IPythonExecutionFactory { createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise; createCondaExecutionService( pythonPath: string, - processService?: IProcessService, - resource?: Uri, + processService: IProcessService, ): Promise; } export const IPythonExecutionService = Symbol('IPythonExecutionService'); @@ -102,6 +101,15 @@ export interface IPythonExecutionService { execModule(moduleName: string, args: string[], options: SpawnOptions): Promise>; } +export interface IPythonEnvironment { + getInterpreterInformation(): Promise; + getExecutionObservableInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo; + getExecutablePath(): Promise; + isModuleInstalled(moduleName: string): Promise; + getModuleVersion(moduleName: string): Promise; + getExecutionInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo; +} + export class StdErrError extends Error { constructor(message: string) { super(message); diff --git a/src/test/common/process/pythonEnvironment.unit.test.ts b/src/test/common/process/pythonEnvironment.unit.test.ts index 06f93d7a3136..4babf9e6013b 100644 --- a/src/test/common/process/pythonEnvironment.unit.test.ts +++ b/src/test/common/process/pythonEnvironment.unit.test.ts @@ -3,6 +3,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../../client/common/platform/types'; @@ -13,6 +14,8 @@ import { } from '../../../client/common/process/pythonEnvironment'; import { IProcessService, StdErrError } from '../../../client/common/process/types'; import { Architecture } from '../../../client/common/utils/platform'; +import { Conda } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; +import { OUTPUT_MARKER_SCRIPT } from '../../../client/common/process/internal/scripts'; use(chaiAsPromised); @@ -275,64 +278,67 @@ suite('CondaEnvironment', () => { const condaFile = 'path/to/conda'; setup(() => { + sinon.stub(Conda, 'getConda').resolves(new Conda(condaFile)); processService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); fileSystem = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); }); - test('getExecutionInfo with a named environment should return execution info using the environment name', () => { + teardown(() => sinon.restore()); + + test('getExecutionInfo with a named environment should return execution info using the environment name', async () => { const condaInfo = { name: 'foo', path: 'bar' }; - const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object); + const env = await createCondaEnv(condaInfo, pythonPath, processService.object, fileSystem.object); - const result = env.getExecutionInfo(args); + const result = env?.getExecutionInfo(args, pythonPath); expect(result).to.deep.equal({ command: condaFile, - args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', ...args], - python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python'], - pythonExecutable: 'python', + args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT, ...args], + python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT], + pythonExecutable: pythonPath, }); }); - test('getExecutionInfo with a non-named environment should return execution info using the environment path', () => { + test('getExecutionInfo with a non-named environment should return execution info using the environment path', async () => { const condaInfo = { name: '', path: 'bar' }; - const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object); + const env = await createCondaEnv(condaInfo, pythonPath, processService.object, fileSystem.object); - const result = env.getExecutionInfo(args); + const result = env?.getExecutionInfo(args, pythonPath); expect(result).to.deep.equal({ command: condaFile, - args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', ...args], - python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python'], - pythonExecutable: 'python', + args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT, ...args], + python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT], + pythonExecutable: pythonPath, }); }); - test('getExecutionObservableInfo with a named environment should return execution info using conda full path with the name', () => { + test('getExecutionObservableInfo with a named environment should return execution info using conda full path with the name', async () => { const condaInfo = { name: 'foo', path: 'bar' }; const expected = { command: condaFile, - args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', ...args], - python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python'], - pythonExecutable: 'python', + args: ['run', '-n', condaInfo.name, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT, ...args], + python: [condaFile, 'run', '-n', condaInfo.name, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT], + pythonExecutable: pythonPath, }; - const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object); + const env = await createCondaEnv(condaInfo, pythonPath, processService.object, fileSystem.object); - const result = env.getExecutionObservableInfo(args); + const result = env?.getExecutionObservableInfo(args, pythonPath); expect(result).to.deep.equal(expected); }); - test('getExecutionObservableInfo with a non-named environment should return execution info using conda full path', () => { + test('getExecutionObservableInfo with a non-named environment should return execution info using conda full path', async () => { const condaInfo = { name: '', path: 'bar' }; const expected = { command: condaFile, - args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', ...args], - python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python'], - pythonExecutable: 'python', + args: ['run', '-p', condaInfo.path, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT, ...args], + python: [condaFile, 'run', '-p', condaInfo.path, '--no-capture-output', 'python', OUTPUT_MARKER_SCRIPT], + pythonExecutable: pythonPath, }; - const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object); + const env = await createCondaEnv(condaInfo, pythonPath, processService.object, fileSystem.object); - const result = env.getExecutionObservableInfo(args); + const result = env?.getExecutionObservableInfo(args, pythonPath); expect(result).to.deep.equal(expected); }); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 6b5e9d4a25db..7dce32bb5d0d 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -16,7 +16,7 @@ import { ConfigurationService } from '../../../client/common/configuration/servi import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessLogger } from '../../../client/common/process/logger'; import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; -import { CONDA_RUN_VERSION, PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; +import { PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; import { IBufferDecoder, IProcessLogger, @@ -28,12 +28,12 @@ import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } f import { Architecture } from '../../../client/common/utils/platform'; import { EnvironmentActivationService } from '../../../client/interpreter/activation/service'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { IComponentAdapter, ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; +import { IComponentAdapter, IInterpreterService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { ServiceContainer } from '../../../client/ioc/container'; -import { CondaService } from '../../../client/pythonEnvironments/common/environmentManagers/condaService'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; import { IInterpreterAutoSelectionService } from '../../../client/interpreter/autoSelection/types'; +import { Conda, CONDA_RUN_VERSION } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; const pythonInterpreter: PythonEnvironment = { path: '/foo/bar/python.exe', @@ -78,7 +78,6 @@ suite('Process - PythonExecutionFactory', () => { let bufferDecoder: IBufferDecoder; let processFactory: IProcessServiceFactory; let configService: IConfigurationService; - let condaService: ICondaService; let processLogger: IProcessLogger; let processService: typemoq.IMock; let interpreterService: IInterpreterService; @@ -87,11 +86,11 @@ suite('Process - PythonExecutionFactory', () => { let autoSelection: IInterpreterAutoSelectionService; let interpreterPathExpHelper: IInterpreterPathService; setup(() => { + sinon.stub(Conda, 'getConda').resolves(new Conda('conda')); bufferDecoder = mock(BufferDecoder); activationHelper = mock(EnvironmentActivationService); processFactory = mock(ProcessServiceFactory); configService = mock(ConfigurationService); - condaService = mock(CondaService); processLogger = mock(ProcessLogger); autoSelection = mock(); interpreterPathExpHelper = mock(); @@ -134,7 +133,6 @@ suite('Process - PythonExecutionFactory', () => { instance(activationHelper), instance(processFactory), instance(configService), - instance(condaService), instance(bufferDecoder), instance(pyenvs), instance(autoSelection), @@ -265,21 +263,18 @@ suite('Process - PythonExecutionFactory', () => { when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); when(pyenvs.getCondaEnvironment(pythonPath)).thenResolve({ name: 'foo', path: 'path/to/foo/env', }); - when(condaService.getCondaFile()).thenResolve('conda'); const service = await factory.create({ resource }); expect(service).to.not.equal(undefined); verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); - verify(condaService.getCondaVersion()).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); }); test('Ensure `create` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => { @@ -288,7 +283,7 @@ suite('Process - PythonExecutionFactory', () => { when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer('1.0.0')); when(interpreterService.hasInterpreters()).thenResolve(true); const service = await factory.create({ resource }); @@ -296,9 +291,7 @@ suite('Process - PythonExecutionFactory', () => { expect(service).to.not.equal(undefined); verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); - verify(condaService.getCondaVersion()).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); }); test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => { @@ -311,17 +304,15 @@ suite('Process - PythonExecutionFactory', () => { x: '1', }); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); when(pyenvs.getCondaEnvironment(anyString())).thenResolve({ name: 'foo', path: 'path/to/foo/env', }); - when(condaService.getCondaFile()).thenResolve('conda'); const service = await factory.createActivatedEnvironment({ resource, interpreter }); expect(service).to.not.equal(undefined); - verify(condaService.getCondaFile()).once(); if (!interpreter) { verify(pythonSettings.pythonPath).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); @@ -346,14 +337,12 @@ suite('Process - PythonExecutionFactory', () => { }); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer('1.0.0')); const service = await factory.createActivatedEnvironment({ resource, interpreter }); expect(service).to.not.equal(undefined); - verify(condaService.getCondaFile()).once(); verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); - verify(condaService.getCondaVersion()).once(); if (!interpreter) { verify(pythonSettings.pythonPath).once(); } @@ -367,40 +356,18 @@ suite('Process - PythonExecutionFactory', () => { name: 'foo', path: 'path/to/foo/env', }); - when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); - when(condaService.getCondaFile()).thenResolve('conda'); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); - const result = await factory.createCondaExecutionService(pythonPath, processService.object, resource); - - expect(result).to.not.equal(undefined); - verify(condaService.getCondaVersion()).once(); - verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); - }); - - test('Ensure `createCondaExecutionService` instantiates a ProcessService instance if the process argument is undefined', async () => { - const pythonPath = 'path/to/python'; - when(processFactory.create(resource)).thenResolve(processService.object); - when(pyenvs.getCondaEnvironment(pythonPath)).thenResolve({ - name: 'foo', - path: 'path/to/foo/env', - }); - when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); - when(condaService.getCondaFile()).thenResolve('conda'); - - const result = await factory.createCondaExecutionService(pythonPath, undefined, resource); + const result = await factory.createCondaExecutionService(pythonPath, processService.object); expect(result).to.not.equal(undefined); - verify(processFactory.create(resource)).once(); - verify(condaService.getCondaVersion()).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); }); test('Ensure `createCondaExecutionService` returns undefined if there is no conda environment', async () => { const pythonPath = 'path/to/python'; when(pyenvs.getCondaEnvironment(pythonPath)).thenResolve(undefined); - when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); const result = await factory.createCondaExecutionService(pythonPath, processService.object); @@ -408,14 +375,12 @@ suite('Process - PythonExecutionFactory', () => { undefined, 'createCondaExecutionService should return undefined if not in a conda environment', ); - verify(condaService.getCondaVersion()).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); }); test('Ensure `createCondaExecutionService` returns undefined if the conda version does not support conda run', async () => { const pythonPath = 'path/to/python'; - when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer('1.0.0')); const result = await factory.createCondaExecutionService(pythonPath, processService.object); @@ -423,9 +388,7 @@ suite('Process - PythonExecutionFactory', () => { undefined, 'createCondaExecutionService should return undefined if not in a conda environment', ); - verify(condaService.getCondaVersion()).once(); verify(pyenvs.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).once(); }); }); }); diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index 01ab1c9bf8db..1a51b7b1f6bf 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -29,13 +29,14 @@ import { import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; -import { IComponentAdapter, ICondaService, IInterpreterService } from '../../client/interpreter/contracts'; +import { IComponentAdapter, IInterpreterService } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { LINTERID_BY_PRODUCT } from '../../client/linters/constants'; import { ILintMessage, LinterId, LintMessageSeverity } from '../../client/linters/types'; import { deleteFile, PYTHON_PATH } from '../common'; import { BaseTestFixture, getLinterID, getProductName, newMockDocument, throwUnknownProduct } from './common'; import { IInterpreterAutoSelectionService } from '../../client/interpreter/autoSelection/types'; +import { Conda } from '../../client/pythonEnvironments/common/environmentManagers/conda'; const workspaceDir = path.join(__dirname, '..', '..', '..', 'src', 'test'); const workspaceUri = Uri.file(workspaceDir); @@ -704,9 +705,8 @@ class TestFixture extends BaseTestFixture { .setup((c) => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())) .returns(() => interpreterService.object); - const condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); - condaService.setup((c) => c.getCondaVersion()).returns(() => Promise.resolve(undefined)); - condaService.setup((c) => c.getCondaFile()).returns(() => Promise.resolve('conda')); + sinon.stub(Conda, 'getConda').resolves(new Conda('conda')); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(undefined); const processLogger = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); processLogger @@ -731,7 +731,6 @@ class TestFixture extends BaseTestFixture { envActivationService.object, procServiceFactory, configService, - condaService.object, decoder, instance(pyenvs), instance(autoSelection), diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 65ba2a27f35f..c8705e59f7cf 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -4,6 +4,7 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; +import * as sinon from 'sinon'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; @@ -15,6 +16,9 @@ import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../.. import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; +import { Conda, CONDA_RUN_VERSION } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; +import { SemVer } from 'semver'; +import assert from 'assert'; suite('Terminal - Django Shell Code Execution', () => { let executor: ICodeExecutionService; @@ -69,6 +73,7 @@ suite('Terminal - Django Shell Code Execution', () => { }); disposables = []; + sinon.restore(); }); async function testReplCommandArguments( @@ -204,7 +209,12 @@ suite('Terminal - Django Shell Code Execution', () => { const condaFile = 'conda'; const processService = TypeMoq.Mock.ofType(); - const env = createCondaEnv(condaFile, condaEnv, pythonPath, processService.object, fileSystem.object); + sinon.stub(Conda, 'getConda').resolves(new Conda(condaFile)); + const env = await createCondaEnv(condaEnv, pythonPath, processService.object, fileSystem.object); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); + if (!env) { + assert(false, 'Should not be undefined for conda version 4.9.0'); + } const procs = createPythonProcessService(processService.object, env); const condaExecutionService = { getInterpreterInformation: env.getInterpreterInformation, @@ -219,7 +229,7 @@ suite('Terminal - Django Shell Code Execution', () => { }; const expectedTerminalArgs = [...terminalArgs, 'manage.py', 'shell']; pythonExecutionFactory - .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(condaExecutionService)); const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 8fcc308d99e8..ba2ab27f6a6c 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; @@ -17,11 +18,14 @@ import { } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; +import { Conda, CONDA_RUN_VERSION } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; +import * as sinon from 'sinon'; +import assert from 'assert'; suite('Terminal - Code Execution', () => { ['Terminal Execution', 'Repl Execution', 'Django Execution'].forEach((testSuiteName) => { @@ -47,7 +51,7 @@ suite('Terminal - Code Execution', () => { disposable.dispose(); } }); - + sinon.restore(); disposables = []; }); @@ -307,9 +311,7 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup((t) => t.executeInFileDir).returns(() => false); workspace.setup((w) => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); pythonExecutionFactory - .setup((p) => - p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - ) + .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(undefined)); await executor.executeFile(file); @@ -355,7 +357,12 @@ suite('Terminal - Code Execution', () => { const condaFile = 'conda'; const procService = TypeMoq.Mock.ofType(); - const env = createCondaEnv(condaFile, condaEnv, pythonPath, procService.object, fileSystem.object); + sinon.stub(Conda, 'getConda').resolves(new Conda(condaFile)); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); + const env = await createCondaEnv(condaEnv, pythonPath, procService.object, fileSystem.object); + if (!env) { + assert(false, 'Should not be undefined for conda version 4.9.0'); + } const procs = createPythonProcessService(procService.object, env); const condaExecutionService = { getInterpreterInformation: env.getInterpreterInformation, @@ -369,9 +376,7 @@ suite('Terminal - Code Execution', () => { execModule: procs.execModule, }; pythonExecutionFactory - .setup((p) => - p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - ) + .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(condaExecutionService)); await executor.executeFile(file); @@ -407,9 +412,7 @@ suite('Terminal - Code Execution', () => { terminalArgs: string[], ) { pythonExecutionFactory - .setup((p) => - p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - ) + .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(undefined)); platform.setup((p) => p.isWindows).returns(() => isWindows); settings.setup((s) => s.pythonPath).returns(() => pythonPath); @@ -467,7 +470,12 @@ suite('Terminal - Code Execution', () => { const condaFile = 'conda'; const procService = TypeMoq.Mock.ofType(); - const env = createCondaEnv(condaFile, condaEnv, pythonPath, procService.object, fileSystem.object); + sinon.stub(Conda, 'getConda').resolves(new Conda(condaFile)); + sinon.stub(Conda.prototype, 'getCondaVersion').resolves(new SemVer(CONDA_RUN_VERSION)); + const env = await createCondaEnv(condaEnv, pythonPath, procService.object, fileSystem.object); + if (!env) { + assert(false, 'Should not be undefined for conda version 4.9.0'); + } const procs = createPythonProcessService(procService.object, env); const condaExecutionService = { getInterpreterInformation: env.getInterpreterInformation, @@ -481,9 +489,7 @@ suite('Terminal - Code Execution', () => { execModule: procs.execModule, }; pythonExecutionFactory - .setup((p) => - p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), - ) + .setup((p) => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(condaExecutionService)); const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : [];