From 66468a076d6215777ecccd53d7884b0a438b5645 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 28 Oct 2019 13:53:48 -0700 Subject: [PATCH 01/58] Initial commit for conda run --- news/3 Code Health/7696.md | 1 + .../common/process/condaExecutionService.ts | 35 ++++++++++ .../common/process/pythonExecutionFactory.ts | 13 ++++ src/client/common/process/pythonProcess.ts | 41 +++++++----- src/client/common/process/types.ts | 5 ++ src/client/terminals/codeExecution/repl.ts | 6 +- .../codeExecution/terminalCodeExecution.ts | 55 ++++++++++++---- .../pythonExecutionFactory.unit.test.ts | 18 +++-- src/test/linters/lint.functional.test.ts | 4 ++ src/test/refactor/rename.test.ts | 17 ++++- .../djangoShellCodeExect.unit.test.ts | 50 +++++++++----- .../terminalCodeExec.unit.test.ts | 66 ++++++++++++++----- .../testing/pytest/pytest.discovery.test.ts | 3 +- src/test/testing/pytest/pytest.run.test.ts | 6 +- 14 files changed, 248 insertions(+), 72 deletions(-) create mode 100644 news/3 Code Health/7696.md create mode 100644 src/client/common/process/condaExecutionService.ts diff --git a/news/3 Code Health/7696.md b/news/3 Code Health/7696.md new file mode 100644 index 000000000000..6ee0d26a91db --- /dev/null +++ b/news/3 Code Health/7696.md @@ -0,0 +1 @@ +Add support "conda run". diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts new file mode 100644 index 000000000000..6005c4843dd5 --- /dev/null +++ b/src/client/common/process/condaExecutionService.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { injectable } from 'inversify'; +import { IServiceContainer } from '../../ioc/types'; +import { PythonExecutionService } from './pythonProcess'; +import { IProcessService, IPythonExecutableInfo } from './types'; + +@injectable() +export class CondaExecutionService extends PythonExecutionService { + constructor( + serviceContainer: IServiceContainer, + procService: IProcessService, + pythonPath: string, + private readonly condaFile: string, + private readonly condaEnvironment: { name: string; path: string } + ) { + super(serviceContainer, procService, pythonPath); + } + + protected getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { + if (this.condaEnvironment.name) { + return { + command: this.condaFile, + args: ['run', '-n', this.condaEnvironment.name, 'python', ...args] + }; + } + if (this.condaEnvironment.path) { + return { + command: this.condaFile, + args: ['run', '-p', this.condaEnvironment.path, 'python', ...args] + }; + } + return { command, args }; + } +} diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 73473af15818..576bb7af6d4b 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -3,12 +3,14 @@ import { inject, injectable } from 'inversify'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; +import { ICondaService } from '../../interpreter/contracts'; import { WindowsStoreInterpreter } from '../../interpreter/locators/services/windowsStoreInterpreter'; import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IConfigurationService, IDisposableRegistry } from '../types'; +import { CondaExecutionService } from './condaExecutionService'; import { ProcessService } from './proc'; import { PythonExecutionService } from './pythonProcess'; import { @@ -30,6 +32,7 @@ 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(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter ) {} @@ -38,6 +41,16 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processService: IProcessService = await this.processServiceFactory.create(options.resource); const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); + + if (options.pythonPath && this.condaService.isCondaEnvironment(options.pythonPath)) { + const condaFile = await this.condaService.getCondaFile(); + const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); + + if (condaEnvironment) { + return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + } + } + if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter); } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 9d7d15333768..6717d0e12e0c 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -16,6 +16,7 @@ import { ExecutionResult, InterpreterInfomation, IProcessService, + IPythonExecutableInfo, IPythonExecutionService, ObservableExecutionResult, PythonVersionInfo, @@ -26,11 +27,7 @@ import { export class PythonExecutionService implements IPythonExecutionService { private readonly fileSystem: IFileSystem; - constructor( - serviceContainer: IServiceContainer, - private readonly procService: IProcessService, - protected readonly pythonPath: string - ) { + constructor(serviceContainer: IServiceContainer, private readonly procService: IProcessService, protected readonly pythonPath: string) { this.fileSystem = serviceContainer.get(IFileSystem); } @@ -41,8 +38,10 @@ export class PythonExecutionService implements IPythonExecutionService { // See these two bugs: // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 - const jsonValue = await waitForPromise(this.procService.exec(this.pythonPath, [file], { mergeStdOutErr: true }), 5000) - .then(output => output ? output.stdout.trim() : '--timed out--'); // --timed out-- should cause an exception + const { command, args } = this.getExecutableInfo(this.pythonPath, [file]); + const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000).then(output => + output ? output.stdout.trim() : '--timed out--' + ); // --timed out-- should cause an exception let json: { versionInfo: PythonVersionInfo; sysPrefix: string; sysVersion: string; is64Bit: boolean }; try { @@ -69,29 +68,37 @@ export class PythonExecutionService implements IPythonExecutionService { if (await this.fileSystem.fileExists(this.pythonPath)) { return this.pythonPath; } - return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { throwOnStdErr: true }) - .then(output => output.stdout.trim()); + + const { command, args } = this.getExecutableInfo(this.pythonPath, ['-c', 'import sys;print(sys.executable)']); + return this.procService.exec(command, args, { throwOnStdErr: true }).then(output => output.stdout.trim()); } public async isModuleInstalled(moduleName: string): Promise { - return this.procService.exec(this.pythonPath, ['-c', `import ${moduleName}`], { throwOnStdErr: true }) - .then(() => true).catch(() => false); + const { command, args } = this.getExecutableInfo(this.pythonPath, ['-c', `import ${moduleName}`]); + return this.procService + .exec(command, args, { throwOnStdErr: true }) + .then(() => true) + .catch(() => false); } public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - return this.procService.execObservable(this.pythonPath, args, opts); + const executable = this.getExecutableInfo(this.pythonPath, args); + return this.procService.execObservable(executable.command, executable.args, opts); } public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - return this.procService.execObservable(this.pythonPath, ['-m', moduleName, ...args], opts); + const executable = this.getExecutableInfo(this.pythonPath, ['-m', moduleName, ...args]); + return this.procService.execObservable(executable.command, executable.args, opts); } public async exec(args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - return this.procService.exec(this.pythonPath, args, opts); + const executable = this.getExecutableInfo(this.pythonPath, args); + return this.procService.exec(executable.command, executable.args, opts); } public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - const result = await this.procService.exec(this.pythonPath, ['-m', moduleName, ...args], opts); + const executable = this.getExecutableInfo(this.pythonPath, ['-m', moduleName, ...args]); + const result = await this.procService.exec(executable.command, executable.args, opts); // If a module is not installed we'll have something in stderr. if (moduleName && ErrorUtils.outputHasModuleNotInstalledError(moduleName!, result.stderr)) { @@ -103,4 +110,8 @@ export class PythonExecutionService implements IPythonExecutionService { return result; } + + protected getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { + return { command, args }; + } } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index de044693fa2e..3863b257753a 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -96,6 +96,11 @@ export interface IPythonExecutionService { execModule(moduleName: string, args: string[], options: SpawnOptions): Promise>; } +export interface IPythonExecutableInfo { + command: string; + args: string[]; +} + export class StdErrError extends Error { constructor(message: string) { super(message); diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index c40cc3792995..1adabe1d4b86 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -9,6 +9,7 @@ import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() @@ -17,11 +18,12 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(ICondaService) condaService: ICondaService, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, interpreterService, condaService, platformService); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index c80089d196f4..78194d6414e4 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,8 +9,10 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; +import { IPythonExecutableInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { ICondaService, IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { ICodeExecutionService } from '../../terminals/types'; @injectable() @@ -22,18 +24,16 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IPlatformService) protected readonly platformService: IPlatformService) { + @inject(IInterpreterService) protected readonly interpreterService: IInterpreterService, + @inject(ICondaService) protected readonly condaService: ICondaService, + @inject(IPlatformService) protected readonly platformService: IPlatformService + ) {} - } public async executeFile(file: Uri) { - const pythonSettings = this.configurationService.getSettings(file); - await this.setCwdForFileExecution(file); + const { command, args } = await this.getReplCommandArgs(file, [file.fsPath.fileToCommandArgument()]); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const launchArgs = pythonSettings.terminal.launchArgs; - - await this.getTerminalService(file).sendCommand(command, launchArgs.concat(file.fsPath.fileToCommandArgument())); + await this.getTerminalService(file).sendCommand(command, args); } public async execute(code: string, resource?: Uri): Promise { @@ -50,7 +50,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { return; } this.replActive = new Promise(async resolve => { - const replCommandArgs = this.getReplCommandArgs(resource); + const replCommandArgs = await this.getExecutableInfo(resource); await this.getTerminalService(resource).sendCommand(replCommandArgs.command, replCommandArgs.args); // Give python repl time to start before we start sending text. @@ -59,11 +59,40 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.replActive; } - public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { + public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { const pythonSettings = this.configurationService.getSettings(resource); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal.launchArgs.slice(); - return { command, args }; + const command = pythonSettings.pythonPath; + const launchArgs = pythonSettings.terminal.launchArgs; + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + + if (interpreter && interpreter.type === InterpreterType.Conda) { + const condaFile = await this.condaService.getCondaFile(); + + if (interpreter.envName) { + return { + command: condaFile, + args: ['run', '-n', interpreter.envName, 'python', ...launchArgs, ...args] + }; + } + + if (interpreter.envPath) { + return { + command: condaFile, + args: ['run', '-p', interpreter.envPath, 'python', ...launchArgs, ...args] + }; + } + } + + const isWindows = this.platformService.isWindows; + + return { + command: isWindows ? command.replace(/\\/g, '/') : command, + args: [...launchArgs, ...args] + }; + } + + public async getReplCommandArgs(resource?: Uri, replArgs: string[] = []): Promise { + return this.getExecutableInfo(resource, replArgs); } private getTerminalService(resource?: Uri): ITerminalService { if (!this._terminalService) { diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index c21550ef61ea..64bfae58a421 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -26,7 +26,8 @@ import { IConfigurationService, IDisposableRegistry } from '../../../client/comm import { Architecture } from '../../../client/common/utils/platform'; import { EnvironmentActivationService } from '../../../client/interpreter/activation/service'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; +import { ICondaService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; +import { CondaService } from '../../../client/interpreter/locators/services/condaService'; import { WindowsStoreInterpreter } from '../../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IWindowsStoreInterpreter } from '../../../client/interpreter/locators/types'; import { ServiceContainer } from '../../../client/ioc/container'; @@ -71,6 +72,7 @@ suite('Process - PythonExecutionFactory', () => { let bufferDecoder: IBufferDecoder; let procecssFactory: IProcessServiceFactory; let configService: IConfigurationService; + let condaService: ICondaService; let processLogger: IProcessLogger; let processService: ProcessService; let windowsStoreInterpreter: IWindowsStoreInterpreter; @@ -79,6 +81,7 @@ suite('Process - PythonExecutionFactory', () => { activationHelper = mock(EnvironmentActivationService); procecssFactory = mock(ProcessServiceFactory); configService = mock(ConfigurationService); + condaService = mock(CondaService); processLogger = mock(ProcessLogger); windowsStoreInterpreter = mock(WindowsStoreInterpreter); when(processLogger.logProcess('', [], {})).thenReturn(); @@ -87,10 +90,15 @@ suite('Process - PythonExecutionFactory', () => { const serviceContainer = mock(ServiceContainer); when(serviceContainer.get(IDisposableRegistry)).thenReturn([]); when(serviceContainer.get(IProcessLogger)).thenReturn(processLogger); - factory = new PythonExecutionFactory(instance(serviceContainer), - instance(activationHelper), instance(procecssFactory), - instance(configService), instance(bufferDecoder), - instance(windowsStoreInterpreter)); + factory = new PythonExecutionFactory( + instance(serviceContainer), + instance(activationHelper), + instance(procecssFactory), + instance(configService), + instance(condaService), + instance(bufferDecoder), + instance(windowsStoreInterpreter) + ); }); test('Ensure PythonExecutionService is created', async () => { diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index 9672612ab842..fa86ccef047b 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -36,6 +36,7 @@ import { import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; +import { ICondaService } from '../../client/interpreter/contracts'; import { WindowsStoreInterpreter } from '../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IServiceContainer } from '../../client/ioc/types'; import { LINTERID_BY_PRODUCT } from '../../client/linters/constants'; @@ -246,6 +247,8 @@ class TestFixture extends BaseTestFixture { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IBufferDecoder), TypeMoq.It.isAny())) .returns(() => decoder); + const condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + const processLogger = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); processLogger .setup(p => p.logProcess(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) @@ -259,6 +262,7 @@ class TestFixture extends BaseTestFixture { envActivationService.object, procServiceFactory, configService, + condaService.object, decoder, instance(windowsStoreInterpreter) ); diff --git a/src/test/refactor/rename.test.ts b/src/test/refactor/rename.test.ts index c7e9ae0c91e6..6f90209c0eb6 100644 --- a/src/test/refactor/rename.test.ts +++ b/src/test/refactor/rename.test.ts @@ -17,6 +17,7 @@ import { PythonExecutionFactory } from '../../client/common/process/pythonExecut import { IProcessLogger, IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../client/common/types'; import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; +import { ICondaService } from '../../client/interpreter/contracts'; import { WindowsStoreInterpreter } from '../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IServiceContainer } from '../../client/ioc/types'; import { RefactorProxy } from '../../client/refactor/proxy'; @@ -39,6 +40,7 @@ suite('Refactor Rename', () => { pythonSettings.setup(p => p.pythonPath).returns(() => PYTHON_PATH); const configService = typeMoq.Mock.ofType(); configService.setup(c => c.getSettings(typeMoq.It.isAny())).returns(() => pythonSettings.object); + const condaService = typeMoq.Mock.ofType(); const processServiceFactory = typeMoq.Mock.ofType(); processServiceFactory.setup(p => p.create(typeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder()))); const envActivationService = typeMoq.Mock.ofType(); @@ -51,9 +53,18 @@ suite('Refactor Rename', () => { const windowsStoreInterpreter = mock(WindowsStoreInterpreter); serviceContainer .setup(s => s.get(typeMoq.It.isValue(IPythonExecutionFactory), typeMoq.It.isAny())) - .returns(() => new PythonExecutionFactory(serviceContainer.object, - undefined as any, processServiceFactory.object, - configService.object, undefined as any, instance(windowsStoreInterpreter))); + .returns( + () => + new PythonExecutionFactory( + serviceContainer.object, + undefined as any, + processServiceFactory.object, + configService.object, + condaService.object, + undefined as any, + instance(windowsStoreInterpreter) + ) + ); const processLogger = typeMoq.Mock.ofType(); processLogger.setup(p => p.logProcess(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny())).returns(() => { return; }); serviceContainer.setup(s => s.get(typeMoq.It.isValue(IProcessLogger), typeMoq.It.isAny())).returns(() => processLogger.object); diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index e85da9496e88..38bb686a91ed 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -11,6 +11,7 @@ import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../c import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; +import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; @@ -39,10 +40,23 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, - workspace.object, documentManager.object, platform.object, commandManager.object, fileSystem.object, disposables); + const interpreterService = TypeMoq.Mock.ofType(); + const condaService = TypeMoq.Mock.ofType(); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + interpreterService.object, + condaService.object, + platform.object, + commandManager.object, + fileSystem.object, + disposables + ); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); @@ -58,13 +72,19 @@ suite('Terminal - Django Shell Code Execution', () => { disposables = []; }); - function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, - terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { + async function testReplCommandArguments( + isWindows: boolean, + pythonPath: string, + expectedPythonPath: string, + terminalArgs: string[], + expectedTerminalArgs: string[], + resource?: Uri + ) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - const replCommandArgs = (executor as DjangoShellCodeExecutionProvider).getReplCommandArgs(resource); + const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); @@ -75,7 +95,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { @@ -83,7 +103,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, when building repl args on Windows', async () => { @@ -91,7 +111,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, on non Windows', async () => { @@ -99,7 +119,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, on non Windows', async () => { @@ -107,7 +127,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure current workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -118,7 +138,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure current workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -129,7 +149,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -141,7 +161,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -153,7 +173,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); - + }); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 35bffee23ddb..7733aa6e059c 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -12,6 +12,7 @@ import { IFileSystem, IPlatformService } from '../../../client/common/platform/t import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; +import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; @@ -26,6 +27,8 @@ suite('Terminal - Code Execution', () => { let platform: TypeMoq.IMock; let workspaceFolder: TypeMoq.IMock; let settings: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let condaService: TypeMoq.IMock; let disposables: Disposable[] = []; let executor: ICodeExecutionService; let expectedTerminalTitle: string | undefined; @@ -56,6 +59,8 @@ suite('Terminal - Code Execution', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + condaService = TypeMoq.Mock.ofType(); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); @@ -63,11 +68,27 @@ suite('Terminal - Code Execution', () => { switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); + executor = new TerminalCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + disposables, + interpreterService.object, + condaService.object, + platform.object + ); break; } case 'Repl Execution': { - executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); + executor = new ReplProvider( + terminalFactory.object, + configService.object, + workspace.object, + interpreterService.object, + condaService.object, + disposables, + platform.object + ); expectedTerminalTitle = 'REPL'; break; } @@ -76,8 +97,18 @@ suite('Terminal - Code Execution', () => { workspace.setup(w => w.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: noop }; }); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, - platform.object, commandManager.object, fileSystem.object, disposables); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + interpreterService.object, + condaService.object, + platform.object, + commandManager.object, + fileSystem.object, + disposables + ); expectedTerminalTitle = 'Django Shell'; break; } @@ -98,6 +129,7 @@ suite('Terminal - Code Execution', () => { platform.setup(p => p.isLinux).returns(() => isLinux); settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); await executor.initializeRepl(); } @@ -209,6 +241,7 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); terminalSettings.setup(t => t.executeInFileDir).returns(() => false); workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; @@ -236,51 +269,52 @@ suite('Terminal - Code Execution', () => { await testFileExecution(false, PYTHON_PATH, ['-a', '-b', '-c'], file); }); - function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { + async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; - const replCommandArgs = (executor as TerminalCodeExecutionProvider).getReplCommandArgs(); + const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); } - test('Ensure fully qualified python path is escaped when building repl args on Windows', () => { + test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { const pythonPath = 'c:\\program files\\python\\python.exe'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); + await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); }); - test('Ensure fully qualified python path is returned as is, when building repl args on Windows', () => { + test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { const pythonPath = 'c:/program files/python/python.exe'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, when building repl args on Windows', () => { + test('Ensure python path is returned as is, when building repl args on Windows', async () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure fully qualified python path is returned as is, on non Windows', () => { + test('Ensure fully qualified python path is returned as is, on non Windows', async () => { const pythonPath = 'usr/bin/python'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, on non Windows', () => { + test('Ensure python path is returned as is, on non Windows', async () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); test('Ensure nothing happens when blank text is sent to the terminal', async () => { diff --git a/src/test/testing/pytest/pytest.discovery.test.ts b/src/test/testing/pytest/pytest.discovery.test.ts index 2cda90f3f3a8..e2bb6fffb547 100644 --- a/src/test/testing/pytest/pytest.discovery.test.ts +++ b/src/test/testing/pytest/pytest.discovery.test.ts @@ -47,9 +47,10 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { @inject(IEnvironmentActivationService) activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly _configService: IConfigurationService, + @inject(ICondaService) condaService: ICondaService, @inject(WindowsStoreInterpreter) windowsStoreInterpreter: WindowsStoreInterpreter, @inject(IBufferDecoder) decoder: IBufferDecoder) { - super(_serviceContainer, activationHelper, processServiceFactory, _configService, decoder, windowsStoreInterpreter); + super(_serviceContainer, activationHelper, processServiceFactory, _configService, condaService, decoder, windowsStoreInterpreter); } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { const pythonPath = options.interpreter ? options.interpreter.path : this._configService.getSettings(options.resource).pythonPath; diff --git a/src/test/testing/pytest/pytest.run.test.ts b/src/test/testing/pytest/pytest.run.test.ts index 79adf2ac7286..d6bd355951b6 100644 --- a/src/test/testing/pytest/pytest.run.test.ts +++ b/src/test/testing/pytest/pytest.run.test.ts @@ -316,9 +316,11 @@ suite('Unit Tests - pytest - run with mocked process output', () => { @inject(IEnvironmentActivationService) activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly _configService: IConfigurationService, + @inject(ICondaService) condaService: ICondaService, @inject(WindowsStoreInterpreter) windowsStoreInterpreter: WindowsStoreInterpreter, - @inject(IBufferDecoder) decoder: IBufferDecoder) { - super(_serviceContainer, activationHelper, processServiceFactory, _configService, decoder, windowsStoreInterpreter); + @inject(IBufferDecoder) decoder: IBufferDecoder + ) { + super(_serviceContainer, activationHelper, processServiceFactory, _configService, condaService, decoder, windowsStoreInterpreter); } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { const pythonPath = options.interpreter ? options.interpreter.path : this._configService.getSettings(options.resource).pythonPath; From 4fda3b0645a4bbec6e2e31b9afb246b200790263 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 28 Oct 2019 13:55:44 -0700 Subject: [PATCH 02/58] Forgot djangoShellCodeExecution.ts --- .../codeExecution/djangoShellCodeExecution.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 3066ec27fb71..a097fd9a6698 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -11,6 +11,7 @@ import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -20,19 +21,28 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(ICondaService) condaService: ICondaService, @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IDisposableRegistry) disposableRegistry: Disposable[]) { - - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + @inject(IDisposableRegistry) disposableRegistry: Disposable[] + ) { + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, interpreterService, condaService, platformService); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { + + public async getReplCommandArgs(resource?: Uri, replArgs: string[] = []): Promise<{ command: string; args: string[] }> { + const { command } = await this.getExecutableInfo(resource); const pythonSettings = this.configurationService.getSettings(resource); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal.launchArgs.slice(); + const args = pythonSettings.terminal.launchArgs.slice().concat(replArgs); + + return { command, args }; + } + + public async getExecutableInfo(resource?: Uri): Promise<{ command: string; args: string[] }> { + const { command, args } = await super.getExecutableInfo(resource); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; From 73bee3c3597841def9c359772703c3b669e9dccd Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 28 Oct 2019 13:57:24 -0700 Subject: [PATCH 03/58] Fix news file --- news/3 Code Health/7696.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/3 Code Health/7696.md b/news/3 Code Health/7696.md index 6ee0d26a91db..1ae4ae765a3b 100644 --- a/news/3 Code Health/7696.md +++ b/news/3 Code Health/7696.md @@ -1 +1 @@ -Add support "conda run". +Add support for "conda run". From d68445e370312db9a58f6c96f049eb9796b31312 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 28 Oct 2019 16:13:52 -0700 Subject: [PATCH 04/58] Fix single workspace tests --- src/test/common/installer.test.ts | 3 +++ src/test/refactor/extension.refactor.extract.var.test.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index db31b0e52461..a391696c0c4c 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -21,6 +21,8 @@ import { ITerminalHelper } from '../../client/common/terminal/types'; import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IsWindows, ModuleNamePurpose, Product, ProductType } from '../../client/common/types'; import { createDeferred } from '../../client/common/utils/async'; import { getNamesAndValues } from '../../client/common/utils/enum'; +import { ICondaService } from '../../client/interpreter/contracts'; +import { CondaService } from '../../client/interpreter/locators/services/condaService'; import { InterpreterHashProvider } from '../../client/interpreter/locators/services/hashProvider'; import { InterpeterHashProviderFactory } from '../../client/interpreter/locators/services/hashProviderFactory'; import { InterpreterFilter } from '../../client/interpreter/locators/services/interpreterFilter'; @@ -71,6 +73,7 @@ suite('Installer', () => { ioc.serviceManager.addSingletonInstance(IApplicationShell, TypeMoq.Mock.ofType().object); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); ioc.serviceManager.addSingleton(IWorkspaceService, WorkspaceService); + ioc.serviceManager.addSingleton(ICondaService, CondaService); ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); diff --git a/src/test/refactor/extension.refactor.extract.var.test.ts b/src/test/refactor/extension.refactor.extract.var.test.ts index 7652d5398326..acfa756ae500 100644 --- a/src/test/refactor/extension.refactor.extract.var.test.ts +++ b/src/test/refactor/extension.refactor.extract.var.test.ts @@ -5,6 +5,8 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { commands, Position, Range, Selection, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorOptions, Uri, window, workspace } from 'vscode'; import { getTextEditsFromPatch } from '../../client/common/editor'; +import { ICondaService } from '../../client/interpreter/contracts'; +import { CondaService } from '../../client/interpreter/locators/services/condaService'; import { extractVariable } from '../../client/providers/simpleRefactorProvider'; import { RefactorProxy } from '../../client/refactor/proxy'; import { getExtensionSettings, isPythonVersion } from '../common'; @@ -51,6 +53,8 @@ suite('Variable Extraction', () => { ioc.registerCommonTypes(); ioc.registerProcessTypes(); ioc.registerVariableTypes(); + + ioc.serviceManager.addSingleton(ICondaService, CondaService); } async function testingVariableExtraction(shouldError: boolean, startPos: Position, endPos: Position): Promise { From de9dddb05a46d1c743fc8adb838f38f3480e81ca Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 09:16:46 -0700 Subject: [PATCH 05/58] Don't use interpreter in terminal code exec --- .../codeExecution/djangoShellCodeExecution.ts | 7 ++--- src/client/terminals/codeExecution/repl.ts | 5 ++-- .../codeExecution/terminalCodeExecution.ts | 20 ++++++------- .../djangoShellCodeExect.unit.test.ts | 9 +++--- .../terminalCodeExec.unit.test.ts | 28 ++----------------- 5 files changed, 22 insertions(+), 47 deletions(-) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index a097fd9a6698..313592d402c6 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -11,7 +11,7 @@ import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; +import { ICondaService } from '../../interpreter/contracts'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -21,19 +21,18 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, - @inject(IInterpreterService) interpreterService: IInterpreterService, @inject(ICondaService) condaService: ICondaService, @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, @inject(IDisposableRegistry) disposableRegistry: Disposable[] ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, interpreterService, condaService, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public async getReplCommandArgs(resource?: Uri, replArgs: string[] = []): Promise<{ command: string; args: string[] }> { + public async getExecuteFileArgs(resource?: Uri, replArgs: string[] = []): Promise<{ command: string; args: string[] }> { const { command } = await this.getExecutableInfo(resource); const pythonSettings = this.configurationService.getSettings(resource); const args = pythonSettings.terminal.launchArgs.slice().concat(replArgs); diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index 1adabe1d4b86..244fcb01865c 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -9,7 +9,7 @@ import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; +import { ICondaService } from '../../interpreter/contracts'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() @@ -18,12 +18,11 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IInterpreterService) interpreterService: IInterpreterService, @inject(ICondaService) condaService: ICondaService, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, interpreterService, condaService, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 78194d6414e4..6458f8ea58ff 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -12,7 +12,7 @@ import { IPlatformService } from '../../common/platform/types'; import { IPythonExecutableInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService, IInterpreterService, InterpreterType } from '../../interpreter/contracts'; +import { ICondaService } from '../../interpreter/contracts'; import { ICodeExecutionService } from '../../terminals/types'; @injectable() @@ -24,14 +24,13 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IInterpreterService) protected readonly interpreterService: IInterpreterService, @inject(ICondaService) protected readonly condaService: ICondaService, @inject(IPlatformService) protected readonly platformService: IPlatformService ) {} public async executeFile(file: Uri) { await this.setCwdForFileExecution(file); - const { command, args } = await this.getReplCommandArgs(file, [file.fsPath.fileToCommandArgument()]); + const { command, args } = await this.getExecuteFileArgs(file, [file.fsPath.fileToCommandArgument()]); await this.getTerminalService(file).sendCommand(command, args); } @@ -63,22 +62,23 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const pythonSettings = this.configurationService.getSettings(resource); const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; - const interpreter = await this.interpreterService.getActiveInterpreter(resource); - if (interpreter && interpreter.type === InterpreterType.Conda) { + const condaEnvironment = await this.condaService.getCondaEnvironment(pythonSettings.pythonPath); + + if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); - if (interpreter.envName) { + if (condaEnvironment.name) { return { command: condaFile, - args: ['run', '-n', interpreter.envName, 'python', ...launchArgs, ...args] + args: ['run', '-n', condaEnvironment.name, 'python', ...launchArgs, ...args] }; } - if (interpreter.envPath) { + if (condaEnvironment.path) { return { command: condaFile, - args: ['run', '-p', interpreter.envPath, 'python', ...launchArgs, ...args] + args: ['run', '-p', condaEnvironment.path, 'python', ...launchArgs, ...args] }; } } @@ -91,7 +91,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { }; } - public async getReplCommandArgs(resource?: Uri, replArgs: string[] = []): Promise { + public async getExecuteFileArgs(resource?: Uri, replArgs: string[] = []): Promise { return this.getExecutableInfo(resource, replArgs); } private getTerminalService(resource?: Uri): ITerminalService { diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 38bb686a91ed..1265caf098a1 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -11,7 +11,7 @@ import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../c import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; +import { ICondaService } from '../../../client/interpreter/contracts'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; @@ -31,7 +31,9 @@ suite('Terminal - Django Shell Code Execution', () => { terminalService = TypeMoq.Mock.ofType(); const configService = TypeMoq.Mock.ofType(); workspace = TypeMoq.Mock.ofType(); - workspace.setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + workspace + .setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => { return { dispose: () => void 0 }; @@ -40,14 +42,12 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - const interpreterService = TypeMoq.Mock.ofType(); const condaService = TypeMoq.Mock.ofType(); executor = new DjangoShellCodeExecutionProvider( terminalFactory.object, configService.object, workspace.object, documentManager.object, - interpreterService.object, condaService.object, platform.object, commandManager.object, @@ -56,7 +56,6 @@ suite('Terminal - Django Shell Code Execution', () => { ); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 7733aa6e059c..7fc6dcf5709d 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -12,7 +12,7 @@ import { IFileSystem, IPlatformService } from '../../../client/common/platform/t import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; +import { ICondaService } from '../../../client/interpreter/contracts'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; @@ -27,7 +27,6 @@ suite('Terminal - Code Execution', () => { let platform: TypeMoq.IMock; let workspaceFolder: TypeMoq.IMock; let settings: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; let condaService: TypeMoq.IMock; let disposables: Disposable[] = []; let executor: ICodeExecutionService; @@ -59,7 +58,6 @@ suite('Terminal - Code Execution', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - interpreterService = TypeMoq.Mock.ofType(); condaService = TypeMoq.Mock.ofType(); settings = TypeMoq.Mock.ofType(); @@ -68,27 +66,11 @@ suite('Terminal - Code Execution', () => { switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - disposables, - interpreterService.object, - condaService.object, - platform.object - ); + executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, condaService.object, platform.object); break; } case 'Repl Execution': { - executor = new ReplProvider( - terminalFactory.object, - configService.object, - workspace.object, - interpreterService.object, - condaService.object, - disposables, - platform.object - ); + executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, condaService.object, disposables, platform.object); expectedTerminalTitle = 'REPL'; break; } @@ -102,7 +84,6 @@ suite('Terminal - Code Execution', () => { configService.object, workspace.object, documentManager.object, - interpreterService.object, condaService.object, platform.object, commandManager.object, @@ -129,7 +110,6 @@ suite('Terminal - Code Execution', () => { platform.setup(p => p.isLinux).returns(() => isLinux); settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); await executor.initializeRepl(); } @@ -241,7 +221,6 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); terminalSettings.setup(t => t.executeInFileDir).returns(() => false); workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; @@ -273,7 +252,6 @@ suite('Terminal - Code Execution', () => { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); From 6d91b33c49baa386952879b39ecaa5fde1c22ddb Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 13:07:02 -0700 Subject: [PATCH 06/58] Try increasing activation timeout time for smoke tests --- src/test/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/constants.ts b/src/test/constants.ts index 61bb763c3b59..43503dcd0663 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER } from './ciConstants'; -export const MAX_EXTENSION_ACTIVATION_TIME = 120_000; +export const MAX_EXTENSION_ACTIVATION_TIME = 180_000; export const TEST_TIMEOUT = 25000; export const TEST_RETRYCOUNT = 3; export const IS_SMOKE_TEST = process.env.VSC_PYTHON_SMOKE_TEST === '1'; From 2646ea298c2a3e8a66be7dfb16a0e84830a46b14 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 13:33:29 -0700 Subject: [PATCH 07/58] Try different timeouts --- src/test/constants.ts | 2 +- src/test/index.ts | 2 +- src/test/testRunner.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/constants.ts b/src/test/constants.ts index 43503dcd0663..d6a73b3dcec8 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER } from './ciConstants'; -export const MAX_EXTENSION_ACTIVATION_TIME = 180_000; +export const MAX_EXTENSION_ACTIVATION_TIME = 240_000; export const TEST_TIMEOUT = 25000; export const TEST_RETRYCOUNT = 3; export const IS_SMOKE_TEST = process.env.VSC_PYTHON_SMOKE_TEST === '1'; diff --git a/src/test/index.ts b/src/test/index.ts index dc5dabf7b214..d3f90d605ae3 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -102,7 +102,7 @@ function configure(): SetupOptions { * @returns */ function activatePythonExtensionScript() { - const ex = new Error('Failed to initialize Python extension for tests after 2 minutes'); + const ex = new Error('Failed to initialize Python extension for tests after 4 minutes'); let timer: NodeJS.Timer | undefined; const failed = new Promise((_, reject) => { timer = setTimeout(() => reject(ex), MAX_EXTENSION_ACTIVATION_TIME); diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index 8bc01f3ef156..054ec5eba892 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -15,7 +15,7 @@ import { initialize } from './initialize'; // Since we are not running in a tty environment, we just implement the method statically. const tty = require('tty'); if (!tty.getWindowSize) { - tty.getWindowSize = function (): number[] { + tty.getWindowSize = function(): number[] { return [80, 75]; }; } @@ -66,10 +66,10 @@ export async function run(): Promise { * @returns */ function initializationScript() { - const ex = new Error('Failed to initialize Python extension for tests after 2 minutes'); + const ex = new Error('Failed to initialize Python extension for tests after 3 minutes'); let timer: NodeJS.Timer | undefined; const failed = new Promise((_, reject) => { - timer = setTimeout(() => reject(ex), 120_000); + timer = setTimeout(() => reject(ex), 180_000); }); const promise = Promise.race([initialize(), failed]); promise.then(() => clearTimeout(timer!)).catch(() => clearTimeout(timer!)); From 8296bd0878fcb86c9ed08b2392d9813aa246be13 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 16:43:06 -0700 Subject: [PATCH 08/58] try commenting conda in pythonExecutionFactory --- .../common/process/pythonExecutionFactory.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 576bb7af6d4b..3c5e26c45d90 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -10,7 +10,7 @@ import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IConfigurationService, IDisposableRegistry } from '../types'; -import { CondaExecutionService } from './condaExecutionService'; +// import { CondaExecutionService } from './condaExecutionService'; import { ProcessService } from './proc'; import { PythonExecutionService } from './pythonProcess'; import { @@ -32,6 +32,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly configService: IConfigurationService, + // @ts-ignore @inject(ICondaService) private readonly condaService: ICondaService, @inject(IBufferDecoder) private readonly decoder: IBufferDecoder, @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter @@ -42,14 +43,14 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - if (options.pythonPath && this.condaService.isCondaEnvironment(options.pythonPath)) { - const condaFile = await this.condaService.getCondaFile(); - const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); + // if (options.pythonPath && this.condaService.isCondaEnvironment(options.pythonPath)) { + // const condaFile = await this.condaService.getCondaFile(); + // const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); - if (condaEnvironment) { - return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); - } - } + // if (condaEnvironment) { + // return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + // } + // } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter); From 389bb59e63e233fd5b3ff552476dc01663229055 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 17:09:38 -0700 Subject: [PATCH 09/58] Undo timeout changes --- src/test/constants.ts | 2 +- src/test/index.ts | 2 +- src/test/testRunner.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/constants.ts b/src/test/constants.ts index d6a73b3dcec8..61bb763c3b59 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER } from './ciConstants'; -export const MAX_EXTENSION_ACTIVATION_TIME = 240_000; +export const MAX_EXTENSION_ACTIVATION_TIME = 120_000; export const TEST_TIMEOUT = 25000; export const TEST_RETRYCOUNT = 3; export const IS_SMOKE_TEST = process.env.VSC_PYTHON_SMOKE_TEST === '1'; diff --git a/src/test/index.ts b/src/test/index.ts index d3f90d605ae3..dc5dabf7b214 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -102,7 +102,7 @@ function configure(): SetupOptions { * @returns */ function activatePythonExtensionScript() { - const ex = new Error('Failed to initialize Python extension for tests after 4 minutes'); + const ex = new Error('Failed to initialize Python extension for tests after 2 minutes'); let timer: NodeJS.Timer | undefined; const failed = new Promise((_, reject) => { timer = setTimeout(() => reject(ex), MAX_EXTENSION_ACTIVATION_TIME); diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index 054ec5eba892..b46e51a0c134 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -66,10 +66,10 @@ export async function run(): Promise { * @returns */ function initializationScript() { - const ex = new Error('Failed to initialize Python extension for tests after 3 minutes'); + const ex = new Error('Failed to initialize Python extension for tests after 2 minutes'); let timer: NodeJS.Timer | undefined; const failed = new Promise((_, reject) => { - timer = setTimeout(() => reject(ex), 180_000); + timer = setTimeout(() => reject(ex), 120_000); }); const promise = Promise.race([initialize(), failed]); promise.then(() => clearTimeout(timer!)).catch(() => clearTimeout(timer!)); From a79252a4835d2d38f94821a30eaa096a3167775c Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 17:10:59 -0700 Subject: [PATCH 10/58] forgot await lol --- .../common/process/pythonExecutionFactory.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 3c5e26c45d90..08422668a678 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -32,7 +32,6 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly configService: IConfigurationService, - // @ts-ignore @inject(ICondaService) private readonly condaService: ICondaService, @inject(IBufferDecoder) private readonly decoder: IBufferDecoder, @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter @@ -43,14 +42,13 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - // if (options.pythonPath && this.condaService.isCondaEnvironment(options.pythonPath)) { - // const condaFile = await this.condaService.getCondaFile(); - // const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); - - // if (condaEnvironment) { - // return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); - // } - // } + if (options.pythonPath && (await this.condaService.isCondaEnvironment(options.pythonPath))) { + // const condaFile = await this.condaService.getCondaFile(); + // const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); + // if (condaEnvironment) { + // return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + // } + } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter); From bdaa375434e81cfab6081c3d0cbc6724d5055ae1 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 29 Oct 2019 17:38:38 -0700 Subject: [PATCH 11/58] Uncomment things --- src/client/common/process/pythonExecutionFactory.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 08422668a678..7be3909cc717 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -10,7 +10,7 @@ import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IConfigurationService, IDisposableRegistry } from '../types'; -// import { CondaExecutionService } from './condaExecutionService'; +import { CondaExecutionService } from './condaExecutionService'; import { ProcessService } from './proc'; import { PythonExecutionService } from './pythonProcess'; import { @@ -43,11 +43,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { processService.on('exec', processLogger.logProcess.bind(processLogger)); if (options.pythonPath && (await this.condaService.isCondaEnvironment(options.pythonPath))) { - // const condaFile = await this.condaService.getCondaFile(); - // const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); - // if (condaEnvironment) { - // return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); - // } + const condaFile = await this.condaService.getCondaFile(); + const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); + if (condaEnvironment) { + return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + } } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { From e6b37c3da413551cf0f71f6a18bcd1aa573e8502 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 30 Oct 2019 14:10:43 -0700 Subject: [PATCH 12/58] Small fixes + remove empty line --- src/client/common/process/pythonExecutionFactory.ts | 4 ++-- src/client/terminals/codeExecution/terminalCodeExecution.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 7be3909cc717..5864c244dbca 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -42,9 +42,9 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - if (options.pythonPath && (await this.condaService.isCondaEnvironment(options.pythonPath))) { + if (pythonPath && (await this.condaService.isCondaEnvironment(pythonPath))) { const condaFile = await this.condaService.getCondaFile(); - const condaEnvironment = await this.condaService.getCondaEnvironment(options.pythonPath); + const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); if (condaEnvironment) { return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 6458f8ea58ff..ab2f71be58cc 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -62,7 +62,6 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const pythonSettings = this.configurationService.getSettings(resource); const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; - const condaEnvironment = await this.condaService.getCondaEnvironment(pythonSettings.pythonPath); if (condaEnvironment) { From e294b4d87ddbb5870d524729b7b41e8873009e73 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 30 Oct 2019 14:18:37 -0700 Subject: [PATCH 13/58] Reduce PR noise by removing some autoformatting --- .../djangoShellCodeExect.unit.test.ts | 22 ++----------------- .../terminalCodeExec.unit.test.ts | 12 +--------- src/test/testing/pytest/pytest.run.test.ts | 3 +-- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 1265caf098a1..910eeb1d2862 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -43,17 +43,7 @@ suite('Terminal - Django Shell Code Execution', () => { const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); const condaService = TypeMoq.Mock.ofType(); - executor = new DjangoShellCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - documentManager.object, - condaService.object, - platform.object, - commandManager.object, - fileSystem.object, - disposables - ); + executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, condaService.object, platform.object, commandManager.object, fileSystem.object, disposables); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); @@ -71,14 +61,7 @@ suite('Terminal - Django Shell Code Execution', () => { disposables = []; }); - async function testReplCommandArguments( - isWindows: boolean, - pythonPath: string, - expectedPythonPath: string, - terminalArgs: string[], - expectedTerminalArgs: string[], - resource?: Uri - ) { + async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); @@ -174,5 +157,4 @@ suite('Terminal - Django Shell Code Execution', () => { await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); - }); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 7fc6dcf5709d..f3f99062692e 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -79,17 +79,7 @@ suite('Terminal - Code Execution', () => { workspace.setup(w => w.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: noop }; }); - executor = new DjangoShellCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - documentManager.object, - condaService.object, - platform.object, - commandManager.object, - fileSystem.object, - disposables - ); + executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, condaService.object, platform.object, commandManager.object, fileSystem.object, disposables); expectedTerminalTitle = 'Django Shell'; break; } diff --git a/src/test/testing/pytest/pytest.run.test.ts b/src/test/testing/pytest/pytest.run.test.ts index d6bd355951b6..a3435669c020 100644 --- a/src/test/testing/pytest/pytest.run.test.ts +++ b/src/test/testing/pytest/pytest.run.test.ts @@ -318,8 +318,7 @@ suite('Unit Tests - pytest - run with mocked process output', () => { @inject(IConfigurationService) private readonly _configService: IConfigurationService, @inject(ICondaService) condaService: ICondaService, @inject(WindowsStoreInterpreter) windowsStoreInterpreter: WindowsStoreInterpreter, - @inject(IBufferDecoder) decoder: IBufferDecoder - ) { + @inject(IBufferDecoder) decoder: IBufferDecoder) { super(_serviceContainer, activationHelper, processServiceFactory, _configService, condaService, decoder, windowsStoreInterpreter); } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { From 5d8a1d3505108ea9b562e9cbc43d1c12294a7667 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 30 Oct 2019 16:03:49 -0700 Subject: [PATCH 14/58] Update createActivatedEnv + minor refactoring --- src/client/common/process/pythonExecutionFactory.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 5864c244dbca..4a82181bf719 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -42,13 +42,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - if (pythonPath && (await this.condaService.isCondaEnvironment(pythonPath))) { - const condaFile = await this.condaService.getCondaFile(); const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); if (condaEnvironment) { + const condaFile = await this.condaService.getCondaFile(); return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); } - } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter); @@ -67,6 +65,13 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); this.serviceContainer.get(IDisposableRegistry).push(processService); + + const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); + if (condaEnvironment) { + const condaFile = await this.condaService.getCondaFile(); + return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + } + return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } } From 82eafec50f6d979aecf9f078671cca273a316214 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 30 Oct 2019 16:05:17 -0700 Subject: [PATCH 15/58] Add pythonExecutionFactory unit tests --- .../pythonExecutionFactory.unit.test.ts | 110 +++++++++++++++++- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 64bfae58a421..6052af80c5e6 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -4,11 +4,12 @@ import * as assert from 'assert'; import { expect } from 'chai'; import { SemVer } from 'semver'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; +import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessLogger } from '../../../client/common/process/logger'; import { ProcessService } from '../../../client/common/process/proc'; @@ -70,7 +71,7 @@ suite('Process - PythonExecutionFactory', () => { let factory: PythonExecutionFactory; let activationHelper: IEnvironmentActivationService; let bufferDecoder: IBufferDecoder; - let procecssFactory: IProcessServiceFactory; + let processFactory: IProcessServiceFactory; let configService: IConfigurationService; let condaService: ICondaService; let processLogger: IProcessLogger; @@ -79,7 +80,7 @@ suite('Process - PythonExecutionFactory', () => { setup(() => { bufferDecoder = mock(BufferDecoder); activationHelper = mock(EnvironmentActivationService); - procecssFactory = mock(ProcessServiceFactory); + processFactory = mock(ProcessServiceFactory); configService = mock(ConfigurationService); condaService = mock(CondaService); processLogger = mock(ProcessLogger); @@ -93,7 +94,7 @@ suite('Process - PythonExecutionFactory', () => { factory = new PythonExecutionFactory( instance(serviceContainer), instance(activationHelper), - instance(procecssFactory), + instance(processFactory), instance(configService), instance(condaService), instance(bufferDecoder), @@ -103,14 +104,14 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure PythonExecutionService is created', async () => { const pythonSettings = mock(PythonSettings); - when(procecssFactory.create(resource)).thenResolve(instance(processService)); + when(processFactory.create(resource)).thenResolve(instance(processService)); when(activationHelper.getActivatedEnvironmentVariables(resource)).thenResolve({ x: '1' }); when(pythonSettings.pythonPath).thenReturn('HELLO'); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); const service = await factory.create({ resource }); - verify(procecssFactory.create(resource)).once(); + verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); expect(service).instanceOf(PythonExecutionService); }); @@ -159,6 +160,103 @@ suite('Process - PythonExecutionFactory', () => { expect(service).instanceOf(PythonExecutionService); assert.equal(createInvoked, false); }); + + test('Ensure `create` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + + when(processFactory.create(resource)).thenResolve(instance(processService)); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(pythonPath)).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); + when(condaService.getCondaFile()).thenResolve('conda'); + + const service = await factory.create({ resource }); + + verify(processFactory.create(resource)).once(); + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).once(); + expect(service).instanceOf(CondaExecutionService); + }); + + test('Ensure `create` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + when(processFactory.create(resource)).thenResolve(instance(processService)); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); + + const service = await factory.create({ resource }); + + verify(processFactory.create(resource)).once(); + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).never(); + expect(service).instanceOf(PythonExecutionService); + }); + + test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + let createInvoked = false; + const pythonPath = 'path/to/python'; + const mockExecService = 'mockService'; + factory.create = async (_options: ExecutionFactoryCreationOptions) => { + createInvoked = true; + return Promise.resolve((mockExecService as any) as IPythonExecutionService); + }; + + const pythonSettings = mock(PythonSettings); + when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(anyString())).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); + when(condaService.getCondaFile()).thenResolve('conda'); + + const service = await factory.createActivatedEnvironment({ resource, interpreter }); + + verify(condaService.getCondaFile()).once(); + verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); + if (!interpreter) { + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + } else { + verify(condaService.getCondaEnvironment(interpreter.path)).once(); + } + + expect(service).instanceOf(CondaExecutionService); + assert.equal(createInvoked, false); + }); + + test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + let createInvoked = false; + const pythonPath = 'path/to/python'; + const mockExecService = 'mockService'; + factory.create = async (_options: ExecutionFactoryCreationOptions) => { + createInvoked = true; + return Promise.resolve((mockExecService as any) as IPythonExecutionService); + }; + + const pythonSettings = mock(PythonSettings); + when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(anyString())).thenResolve(undefined); + + const service = await factory.createActivatedEnvironment({ resource, interpreter }); + + verify(condaService.getCondaFile()).never(); + verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); + if (!interpreter) { + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + } else { + verify(condaService.getCondaEnvironment(interpreter.path)).once(); + } + + expect(service).instanceOf(PythonExecutionService); + assert.equal(createInvoked, false); + }); }); }); }); From 9b8f9488a4d291fb13f772809fa50b19fc3b5558 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 30 Oct 2019 16:05:17 -0700 Subject: [PATCH 16/58] Add pythonExecutionFactory unit tests --- .../common/process/pythonExecutionFactory.ts | 8 +- .../pythonExecutionFactory.unit.test.ts | 110 +++++++++++++++++- 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 4a82181bf719..1217ba36cd3d 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -42,11 +42,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); - if (condaEnvironment) { + const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); + if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); - return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); - } + return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 64bfae58a421..6052af80c5e6 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -4,11 +4,12 @@ import * as assert from 'assert'; import { expect } from 'chai'; import { SemVer } from 'semver'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; +import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessLogger } from '../../../client/common/process/logger'; import { ProcessService } from '../../../client/common/process/proc'; @@ -70,7 +71,7 @@ suite('Process - PythonExecutionFactory', () => { let factory: PythonExecutionFactory; let activationHelper: IEnvironmentActivationService; let bufferDecoder: IBufferDecoder; - let procecssFactory: IProcessServiceFactory; + let processFactory: IProcessServiceFactory; let configService: IConfigurationService; let condaService: ICondaService; let processLogger: IProcessLogger; @@ -79,7 +80,7 @@ suite('Process - PythonExecutionFactory', () => { setup(() => { bufferDecoder = mock(BufferDecoder); activationHelper = mock(EnvironmentActivationService); - procecssFactory = mock(ProcessServiceFactory); + processFactory = mock(ProcessServiceFactory); configService = mock(ConfigurationService); condaService = mock(CondaService); processLogger = mock(ProcessLogger); @@ -93,7 +94,7 @@ suite('Process - PythonExecutionFactory', () => { factory = new PythonExecutionFactory( instance(serviceContainer), instance(activationHelper), - instance(procecssFactory), + instance(processFactory), instance(configService), instance(condaService), instance(bufferDecoder), @@ -103,14 +104,14 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure PythonExecutionService is created', async () => { const pythonSettings = mock(PythonSettings); - when(procecssFactory.create(resource)).thenResolve(instance(processService)); + when(processFactory.create(resource)).thenResolve(instance(processService)); when(activationHelper.getActivatedEnvironmentVariables(resource)).thenResolve({ x: '1' }); when(pythonSettings.pythonPath).thenReturn('HELLO'); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); const service = await factory.create({ resource }); - verify(procecssFactory.create(resource)).once(); + verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); expect(service).instanceOf(PythonExecutionService); }); @@ -159,6 +160,103 @@ suite('Process - PythonExecutionFactory', () => { expect(service).instanceOf(PythonExecutionService); assert.equal(createInvoked, false); }); + + test('Ensure `create` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + + when(processFactory.create(resource)).thenResolve(instance(processService)); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(pythonPath)).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); + when(condaService.getCondaFile()).thenResolve('conda'); + + const service = await factory.create({ resource }); + + verify(processFactory.create(resource)).once(); + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).once(); + expect(service).instanceOf(CondaExecutionService); + }); + + test('Ensure `create` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + when(processFactory.create(resource)).thenResolve(instance(processService)); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); + + const service = await factory.create({ resource }); + + verify(processFactory.create(resource)).once(); + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).never(); + expect(service).instanceOf(PythonExecutionService); + }); + + test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + let createInvoked = false; + const pythonPath = 'path/to/python'; + const mockExecService = 'mockService'; + factory.create = async (_options: ExecutionFactoryCreationOptions) => { + createInvoked = true; + return Promise.resolve((mockExecService as any) as IPythonExecutionService); + }; + + const pythonSettings = mock(PythonSettings); + when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(anyString())).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); + when(condaService.getCondaFile()).thenResolve('conda'); + + const service = await factory.createActivatedEnvironment({ resource, interpreter }); + + verify(condaService.getCondaFile()).once(); + verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); + if (!interpreter) { + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + } else { + verify(condaService.getCondaEnvironment(interpreter.path)).once(); + } + + expect(service).instanceOf(CondaExecutionService); + assert.equal(createInvoked, false); + }); + + test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + let createInvoked = false; + const pythonPath = 'path/to/python'; + const mockExecService = 'mockService'; + factory.create = async (_options: ExecutionFactoryCreationOptions) => { + createInvoked = true; + return Promise.resolve((mockExecService as any) as IPythonExecutionService); + }; + + const pythonSettings = mock(PythonSettings); + when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaEnvironment(anyString())).thenResolve(undefined); + + const service = await factory.createActivatedEnvironment({ resource, interpreter }); + + verify(condaService.getCondaFile()).never(); + verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); + if (!interpreter) { + verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + } else { + verify(condaService.getCondaEnvironment(interpreter.path)).once(); + } + + expect(service).instanceOf(PythonExecutionService); + assert.equal(createInvoked, false); + }); }); }); }); From 0068e0d0528176eb0ac85cdab0edddcb5414018d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 09:54:22 -0700 Subject: [PATCH 17/58] Fix conda env name check + django terminal --- src/client/common/process/condaExecutionService.ts | 2 +- .../terminals/codeExecution/djangoShellCodeExecution.ts | 9 +++------ .../terminals/codeExecution/terminalCodeExecution.ts | 6 +++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 6005c4843dd5..870fbbdc24d1 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -18,7 +18,7 @@ export class CondaExecutionService extends PythonExecutionService { } protected getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { - if (this.condaEnvironment.name) { + if (this.condaEnvironment.name !== '') { return { command: this.condaFile, args: ['run', '-n', this.condaEnvironment.name, 'python', ...args] diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 313592d402c6..41ed75d22b5b 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -32,12 +32,9 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public async getExecuteFileArgs(resource?: Uri, replArgs: string[] = []): Promise<{ command: string; args: string[] }> { - const { command } = await this.getExecutableInfo(resource); - const pythonSettings = this.configurationService.getSettings(resource); - const args = pythonSettings.terminal.launchArgs.slice().concat(replArgs); - - return { command, args }; + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise<{ command: string; args: string[] }> { + const { command, args } = await super.getExecutableInfo(resource); + return { command, args: args.concat(executeArgs) }; } public async getExecutableInfo(resource?: Uri): Promise<{ command: string; args: string[] }> { diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index ab2f71be58cc..cd4b08748cd0 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -67,7 +67,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); - if (condaEnvironment.name) { + if (condaEnvironment.name !== '') { return { command: condaFile, args: ['run', '-n', condaEnvironment.name, 'python', ...launchArgs, ...args] @@ -90,8 +90,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { }; } - public async getExecuteFileArgs(resource?: Uri, replArgs: string[] = []): Promise { - return this.getExecutableInfo(resource, replArgs); + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { + return this.getExecutableInfo(resource, executeArgs); } private getTerminalService(resource?: Uri): ITerminalService { if (!this._terminalService) { From 637be5f4549507381800ee3e0e907fa722e3935a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 09:55:32 -0700 Subject: [PATCH 18/58] Unit tests for terminalCodeExecution.ts --- .../terminalCodeExec.unit.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index f3f99062692e..201f15d43015 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -238,6 +238,36 @@ suite('Terminal - Code Execution', () => { await testFileExecution(false, PYTHON_PATH, ['-a', '-b', '-c'], file); }); + async function testCondaFileExecution(pythonPath: string, terminalArgs: string[], file: Uri, condaEnv: { name: string; path: string }): Promise { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + terminalSettings.setup(t => t.executeInFileDir).returns(() => false); + workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); + condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + + await executor.executeFile(file); + + const hasEnvName = condaEnv.name !== ''; + const expectedPythonPath = 'conda'; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; + + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + } + + test('Ensure conda args with conda env name are sent to terminal if there is a conda environment with a name', async () => { + const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); + await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: 'foo-env', path: 'path/to/foo-env' }); + }); + + test('Ensure conda args with conda env path are sent to terminal if there is a conda environment without a name', async () => { + const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); + await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: '', path: 'path/to/foo-env' }); + }); + async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); @@ -285,6 +315,32 @@ suite('Terminal - Code Execution', () => { await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); + async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }) { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); + condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + + const hasEnvName = condaEnv.name !== ''; + const expectedPythonPath = 'conda'; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : []; + const expectedTerminalArgs = [...condaArgs, ...terminalArgs, ...djangoArgs]; + + const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); + expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); + expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); + } + + test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { + await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: 'foo-env', path: 'path/to/foo-env' }); + }); + + test('Ensure conda args with env path are returned when building repl args with a conda env without a name', async () => { + await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: '', path: 'path/to/foo-env' }); + }); + test('Ensure nothing happens when blank text is sent to the terminal', async () => { await executor.execute(''); await executor.execute(' '); From ad42ee886dce891a1b82bd5300eaf8236e98f5a9 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 10:53:38 -0700 Subject: [PATCH 19/58] Fix linting functional tests --- src/test/linters/lint.functional.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index fa86ccef047b..76497bdddf0b 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -248,6 +248,7 @@ class TestFixture extends BaseTestFixture { .returns(() => decoder); const condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(undefined)); const processLogger = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); processLogger From 5255e45d86c354e052af74e622d9ca8ac9600245 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 11:38:55 -0700 Subject: [PATCH 20/58] djangoShellCodeExecution tests + fix terminalCodeExec.unit.test.ts --- .../djangoShellCodeExect.unit.test.ts | 37 ++++++++++++++++++- .../terminalCodeExec.unit.test.ts | 8 ++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 910eeb1d2862..78558b119f4f 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -25,7 +25,9 @@ suite('Terminal - Django Shell Code Execution', () => { let platform: TypeMoq.IMock; let settings: TypeMoq.IMock; let disposables: Disposable[] = []; + let condaService: TypeMoq.IMock; setup(() => { + condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); const terminalFactory = TypeMoq.Mock.ofType(); terminalSettings = TypeMoq.Mock.ofType(); terminalService = TypeMoq.Mock.ofType(); @@ -42,7 +44,6 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - const condaService = TypeMoq.Mock.ofType(); executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, condaService.object, platform.object, commandManager.object, fileSystem.object, disposables); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); @@ -65,6 +66,7 @@ suite('Terminal - Django Shell Code Execution', () => { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(undefined)); const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); @@ -157,4 +159,37 @@ suite('Terminal - Django Shell Code Execution', () => { await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); + + async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }, resource?: Uri) { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); + condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + + const hasEnvName = condaEnv.name !== ''; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; + + const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); + + expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); + expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + } + + test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { + const pythonPath = 'c:/program files/python/python.exe'; + const condaPath = { name: 'foo-env', path: 'path/to/foo-env' }; + const terminalArgs = ['-a', 'b', '-c']; + + await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); + }); + + test('Ensure conda args including env path are passed when using a conda environment with an empty name', async () => { + const pythonPath = 'c:/program files/python/python.exe'; + const condaPath = { name: '', path: 'path/to/foo-env' }; + const terminalArgs = ['-a', 'b', '-c']; + + await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); + }); }); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 201f15d43015..3a074e336820 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -322,15 +322,15 @@ suite('Terminal - Code Execution', () => { condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); const hasEnvName = condaEnv.name !== ''; - const expectedPythonPath = 'conda'; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : []; const expectedTerminalArgs = [...condaArgs, ...terminalArgs, ...djangoArgs]; const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); - expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); - expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); - expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); + + expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); + expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); } test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { From 6a0ab56eba0fbf06754d37476ca431f3ac0a62b3 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 11:50:36 -0700 Subject: [PATCH 21/58] Make conda path check more explicit --- src/client/common/process/condaExecutionService.ts | 2 +- src/client/terminals/codeExecution/terminalCodeExecution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 870fbbdc24d1..7694933478e3 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -24,7 +24,7 @@ export class CondaExecutionService extends PythonExecutionService { args: ['run', '-n', this.condaEnvironment.name, 'python', ...args] }; } - if (this.condaEnvironment.path) { + if (this.condaEnvironment.path.length > 0) { return { command: this.condaFile, args: ['run', '-p', this.condaEnvironment.path, 'python', ...args] diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index cd4b08748cd0..7c5ea4944af9 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -74,7 +74,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { }; } - if (condaEnvironment.path) { + if (condaEnvironment.path.length > 0) { return { command: condaFile, args: ['run', '-p', condaEnvironment.path, 'python', ...launchArgs, ...args] From 2a43052d4a569fab2d7264577a3c4a5d52dbdbf5 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 12:04:18 -0700 Subject: [PATCH 22/58] Add Windows test to PythonExecutionFactory --- .../pythonExecutionFactory.unit.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 6052af80c5e6..a0b7ba8de6ef 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -23,6 +23,7 @@ import { IProcessServiceFactory, IPythonExecutionService } from '../../../client/common/process/types'; +import { WindowsStorePythonProcess } from '../../../client/common/process/windowsStorePythonProcess'; import { IConfigurationService, IDisposableRegistry } from '../../../client/common/types'; import { Architecture } from '../../../client/common/utils/platform'; import { EnvironmentActivationService } from '../../../client/interpreter/activation/service'; @@ -161,6 +162,23 @@ suite('Process - PythonExecutionFactory', () => { assert.equal(createInvoked, false); }); + test('Ensure `create` returns a WindowsStorePythonProcess instance if it\'s a windows store intepreter path', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + + when(processFactory.create(resource)).thenResolve(instance(processService)); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(true); + + const service = await factory.create({ resource }); + + verify(processFactory.create(resource)).once(); + verify(pythonSettings.pythonPath).once(); + verify(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).once(); + expect(service).instanceOf(WindowsStorePythonProcess); + }); + test('Ensure `create` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); From 3b304bec4d812a7e1c3ce20ccc0696289e1306d1 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 31 Oct 2019 12:04:58 -0700 Subject: [PATCH 23/58] Add conda call verification --- .../codeExecution/djangoShellCodeExect.unit.test.ts | 4 ++++ .../terminals/codeExecution/terminalCodeExec.unit.test.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 78558b119f4f..4b8a2d5b2881 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -72,6 +72,8 @@ suite('Terminal - Django Shell Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -175,6 +177,8 @@ suite('Terminal - Django Shell Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); } test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 3a074e336820..33691416daf6 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -216,6 +216,8 @@ suite('Terminal - Code Execution', () => { const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -278,6 +280,8 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -331,6 +335,8 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); + condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); } test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { From 1fea07e058ed2d74617c9df509188d1b25ce2b07 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 1 Nov 2019 16:01:58 -0700 Subject: [PATCH 24/58] Add CondaEnvironmentInfo type --- src/client/common/process/condaExecutionService.ts | 3 ++- src/client/interpreter/contracts.ts | 9 +++++++-- src/client/interpreter/locators/services/condaService.ts | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 7694933478e3..cd1eab952407 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { injectable } from 'inversify'; +import { CondaEnvironmentInfo } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { PythonExecutionService } from './pythonProcess'; import { IProcessService, IPythonExecutableInfo } from './types'; @@ -12,7 +13,7 @@ export class CondaExecutionService extends PythonExecutionService { procService: IProcessService, pythonPath: string, private readonly condaFile: string, - private readonly condaEnvironment: { name: string; path: string } + private readonly condaEnvironment: CondaEnvironmentInfo ) { super(serviceContainer, procService, pythonPath); } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 20865aa16094..32837df1b8bd 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -43,6 +43,11 @@ export type CondaInfo = { conda_version?: string; }; +export type CondaEnvironmentInfo = { + name: string; + path: string; +}; + export const ICondaService = Symbol('ICondaService'); export interface ICondaService { @@ -51,11 +56,11 @@ export interface ICondaService { isCondaAvailable(): Promise; getCondaVersion(): Promise; getCondaInfo(): Promise; - getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined>; + getCondaEnvironments(ignoreCache: boolean): Promise; getInterpreterPath(condaEnvironmentPath: string): string; getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise; isCondaEnvironment(interpreterPath: string): Promise; - getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined>; + getCondaEnvironment(interpreterPath: string): Promise; } export enum InterpreterType { diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 304f0bdc7a6d..9a7f2dbc9e0d 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -9,6 +9,7 @@ import { IFileSystem, IPlatformService } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; import { IConfigurationService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../../../common/types'; import { + CondaEnvironmentInfo, CondaInfo, ICondaService, IInterpreterLocatorService, @@ -211,10 +212,10 @@ export class CondaService implements ICondaService { * Return the list of conda envs (by name, interpreter filename). */ @traceDecorators.verbose('Get Conda environments') - public async getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined> { + public async getCondaEnvironments(ignoreCache: boolean): Promise { // Global cache. // tslint:disable-next-line:no-any - const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ data: { name: string; path: string }[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any); + const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ data: CondaEnvironmentInfo[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any); if (!ignoreCache && globalPersistence.value) { return globalPersistence.value.data; } From 136110353e6679772e4f53195f5c87ecbf7e5f0d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 1 Nov 2019 16:39:13 -0700 Subject: [PATCH 25/58] CondaExecutionService unit tests --- .../condaExecutionService.unit.test.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/test/common/process/condaExecutionService.unit.test.ts diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts new file mode 100644 index 000000000000..1d39e6525b28 --- /dev/null +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { expect } from 'chai'; +import * as TypeMoq from 'typemoq'; +import { IFileSystem } from '../../../client/common/platform/types'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; +import { IProcessService } from '../../../client/common/process/types'; +import { CondaEnvironmentInfo } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; + +suite('CondaExecutionService', () => { + let serviceContainer: TypeMoq.IMock; + let processService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let executionService: CondaExecutionService; + const args = ['-a', 'b', '-c']; + const pythonPath = 'path/to/python'; + const condaFile = 'path/to/conda'; + + setup(() => { + processService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + serviceContainer = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + fileSystem = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + + serviceContainer.setup(s => s.get(IFileSystem)).returns(() => fileSystem.object); + }); + + async function testExecutionService(environment: CondaEnvironmentInfo, expectedCommand: string, expectedArgs: string[]): Promise { + const expectedExecResult = { stdout: 'foo' }; + + processService.setup(p => p.exec(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), {})).returns(() => Promise.resolve(expectedExecResult)); + + executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); + + const executableInfo = await executionService.exec(args, {}); + + expect(executableInfo).to.be.equal(expectedExecResult); + processService.verify(p => p.exec(expectedCommand, expectedArgs, {}), TypeMoq.Times.once()); + } + + test('getExecutableInfo with a named environment should return an executable command using the environment name', async () => { + const environment = { name: 'foo', path: 'bar' }; + await testExecutionService(environment, condaFile, ['run', '-n', environment.name, 'python', ...args]); + }); + + test('getExecutableInfo with a non-named environment should return an executable command using the environment npathame', async () => { + const environment = { name: '', path: 'bar' }; + await testExecutionService(environment, condaFile, ['run', '-p', environment.path, 'python', ...args]); + }); + + test('getExecutableInfo with an environment without a name or a prefix should return an executable command using pythonPath', async () => { + const environment = { name: '', path: '' }; + await testExecutionService(environment, pythonPath, args); + }); +}); From fe5601b1b955d56619bcdbc45a4a14f84af1353f Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 4 Nov 2019 10:29:54 -0800 Subject: [PATCH 26/58] Add comment --- src/client/terminals/codeExecution/djangoShellCodeExecution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 41ed75d22b5b..dc315df4968e 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -33,6 +33,7 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi } public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise<{ command: string; args: string[] }> { + // We need the executable info but not the 'manage.py shell' args const { command, args } = await super.getExecutableInfo(resource); return { command, args: args.concat(executeArgs) }; } From 45160bfdde1302c379981ac34a17a729a8108eac Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 6 Nov 2019 08:33:57 -0800 Subject: [PATCH 27/58] Remove autformatting --- src/test/testRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index b46e51a0c134..8bc01f3ef156 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -15,7 +15,7 @@ import { initialize } from './initialize'; // Since we are not running in a tty environment, we just implement the method statically. const tty = require('tty'); if (!tty.getWindowSize) { - tty.getWindowSize = function(): number[] { + tty.getWindowSize = function (): number[] { return [80, 75]; }; } From 7319345c7ffab8ee01ae44918ae346890885fe7f Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 6 Nov 2019 12:05:59 -0800 Subject: [PATCH 28/58] Make getExecutableInfo public --- src/client/common/process/condaExecutionService.ts | 2 +- src/client/common/process/pythonProcess.ts | 2 +- src/client/common/process/types.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index cd1eab952407..51b1492d9e4c 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -18,7 +18,7 @@ export class CondaExecutionService extends PythonExecutionService { super(serviceContainer, procService, pythonPath); } - protected getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { + public getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { if (this.condaEnvironment.name !== '') { return { command: this.condaFile, diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 6717d0e12e0c..a77c32f26e27 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -111,7 +111,7 @@ export class PythonExecutionService implements IPythonExecutionService { return result; } - protected getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { + public getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { return { command, args }; } } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 3863b257753a..5952cb63717e 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -88,6 +88,7 @@ export interface IPythonExecutionService { getInterpreterInformation(): Promise; getExecutablePath(): Promise; isModuleInstalled(moduleName: string): Promise; + getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo; execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult; execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult; From 3e972ac43c7a714f0112ad74cf09b662df204257 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 6 Nov 2019 13:09:50 -0800 Subject: [PATCH 29/58] Use CondaExecutionService in terminal code execution --- .../codeExecution/djangoShellCodeExecution.ts | 9 +++- src/client/terminals/codeExecution/repl.ts | 6 ++- .../codeExecution/terminalCodeExecution.ts | 27 +++++------- src/test/datascience/executionServiceMock.ts | 3 ++ src/test/datascience/mockPythonService.ts | 4 ++ .../djangoShellCodeExect.unit.test.ts | 18 +++++++- .../terminalCodeExec.unit.test.ts | 43 +++++++++++++++++-- 7 files changed, 86 insertions(+), 24 deletions(-) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index dc315df4968e..863a433d34ac 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,15 +9,18 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; +import { IProcessServiceFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICondaService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvider { - constructor(@inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, + constructor( + @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, @@ -25,9 +28,11 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[] ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService, serviceContainer, processServiceFactory); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index 244fcb01865c..58ae0abb400f 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -7,9 +7,11 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; +import { IProcessServiceFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICondaService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() @@ -19,10 +21,12 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(ICondaService) condaService: ICondaService, + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService, serviceContainer, processServiceFactory); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 7c5ea4944af9..84d6629e7b71 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,10 +9,12 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutableInfo } from '../../common/process/types'; +import { CondaExecutionService } from '../../common/process/condaExecutionService'; +import { IProcessService, IProcessServiceFactory, IPythonExecutableInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICondaService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionService } from '../../terminals/types'; @injectable() @@ -20,12 +22,15 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { protected terminalTitle!: string; private _terminalService!: ITerminalService; private replActive?: Promise; - constructor(@inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, + constructor( + @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], @inject(ICondaService) protected readonly condaService: ICondaService, - @inject(IPlatformService) protected readonly platformService: IPlatformService + @inject(IPlatformService) protected readonly platformService: IPlatformService, + @inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer, + @inject(IProcessServiceFactory) protected readonly processServiceFactory: IProcessServiceFactory ) {} public async executeFile(file: Uri) { @@ -66,22 +71,12 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); + const processService: IProcessService = await this.processServiceFactory.create(resource); + const condaExecutionService = new CondaExecutionService(this.serviceContainer, processService, command, condaFile, condaEnvironment); - if (condaEnvironment.name !== '') { - return { - command: condaFile, - args: ['run', '-n', condaEnvironment.name, 'python', ...launchArgs, ...args] - }; + return condaExecutionService.getExecutableInfo(command, [...launchArgs, ...args]); } - if (condaEnvironment.path.length > 0) { - return { - command: condaFile, - args: ['run', '-p', condaEnvironment.path, 'python', ...launchArgs, ...args] - }; - } - } - const isWindows = this.platformService.isWindows; return { diff --git a/src/test/datascience/executionServiceMock.ts b/src/test/datascience/executionServiceMock.ts index aefb095b50cb..15d9f01ebacd 100644 --- a/src/test/datascience/executionServiceMock.ts +++ b/src/test/datascience/executionServiceMock.ts @@ -67,4 +67,7 @@ export class MockPythonExecutionService implements IPythonExecutionService { return result; } + public getExecutableInfo(command: string, args: string[]) { + return { command, args }; + } } diff --git a/src/test/datascience/mockPythonService.ts b/src/test/datascience/mockPythonService.ts index 39293dce94a1..4c25dd44e21f 100644 --- a/src/test/datascience/mockPythonService.ts +++ b/src/test/datascience/mockPythonService.ts @@ -64,4 +64,8 @@ export class MockPythonService implements IPythonExecutionService { public setDelay(timeout: number | undefined) { this.procService.setDelay(timeout); } + + public getExecutableInfo(command: string, args: string[]) { + return { command, args }; + } } diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 4b8a2d5b2881..8db786cc36b6 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -9,9 +9,11 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { ICondaService } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; @@ -44,7 +46,21 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, condaService.object, platform.object, commandManager.object, fileSystem.object, disposables); + const serviceContainer = TypeMoq.Mock.ofType(); + const processServiceFactory = TypeMoq.Mock.ofType(); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + condaService.object, + platform.object, + commandManager.object, + fileSystem.object, + serviceContainer.object, + processServiceFactory.object, + disposables + ); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 33691416daf6..21916afa1499 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -9,10 +9,12 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; import { ICondaService } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; @@ -35,6 +37,8 @@ suite('Terminal - Code Execution', () => { let documentManager: TypeMoq.IMock; let commandManager: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let processServiceFactory: TypeMoq.IMock; let isDjangoRepl: boolean; teardown(() => { @@ -59,18 +63,37 @@ suite('Terminal - Code Execution', () => { commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); condaService = TypeMoq.Mock.ofType(); - + serviceContainer = TypeMoq.Mock.ofType(); + processServiceFactory = TypeMoq.Mock.ofType(); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, condaService.object, platform.object); + executor = new TerminalCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + disposables, + condaService.object, + platform.object, + serviceContainer.object, + processServiceFactory.object + ); break; } case 'Repl Execution': { - executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, condaService.object, disposables, platform.object); + executor = new ReplProvider( + terminalFactory.object, + configService.object, + workspace.object, + condaService.object, + serviceContainer.object, + processServiceFactory.object, + disposables, + platform.object + ); expectedTerminalTitle = 'REPL'; break; } @@ -79,7 +102,19 @@ suite('Terminal - Code Execution', () => { workspace.setup(w => w.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: noop }; }); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, condaService.object, platform.object, commandManager.object, fileSystem.object, disposables); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + condaService.object, + platform.object, + commandManager.object, + fileSystem.object, + serviceContainer.object, + processServiceFactory.object, + disposables + ); expectedTerminalTitle = 'Django Shell'; break; } From 4f7cc14fe5348cc1cc6b79d1a5ae6523fdc54460 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Date: Thu, 7 Nov 2019 14:40:39 -0800 Subject: [PATCH 30/58] Apply suggestions from code review Co-Authored-By: Eric Snow --- news/3 Code Health/7696.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/3 Code Health/7696.md b/news/3 Code Health/7696.md index 1ae4ae765a3b..31014c414703 100644 --- a/news/3 Code Health/7696.md +++ b/news/3 Code Health/7696.md @@ -1 +1,2 @@ -Add support for "conda run". +Use "conda run" (instead of using the "python.pythonPath" setting directly) when executing +Python and an Anaconda environment is selected. From 1719adfafdcdb9837b0dcfdc757aa5fa1c9dc55c Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 7 Nov 2019 11:58:04 -0800 Subject: [PATCH 31/58] refactor CondaExecutionService instantiation --- .../common/process/pythonExecutionFactory.ts | 23 +++++-- src/client/common/process/types.ts | 2 + .../codeExecution/djangoShellCodeExecution.ts | 10 +-- src/client/terminals/codeExecution/repl.ts | 10 +-- .../codeExecution/terminalCodeExecution.ts | 16 ++--- .../djangoShellCodeExect.unit.test.ts | 28 ++++---- .../terminalCodeExec.unit.test.ts | 64 ++++++++----------- 7 files changed, 67 insertions(+), 86 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 1217ba36cd3d..3fd1e00442c1 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; import { ICondaService } from '../../interpreter/contracts'; import { WindowsStoreInterpreter } from '../../interpreter/locators/services/windowsStoreInterpreter'; @@ -42,10 +43,9 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); - if (condaEnvironment) { - const condaFile = await this.condaService.getCondaFile(); - return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { @@ -66,12 +66,25 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { processService.on('exec', processLogger.logProcess.bind(processLogger)); this.serviceContainer.get(IDisposableRegistry).push(processService); + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; + } + + return new PythonExecutionService(this.serviceContainer, processService, pythonPath); + } + public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); + + if (!processService) { + processService = await this.processServiceFactory.create(resource); + } + return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); } - return new PythonExecutionService(this.serviceContainer, processService, pythonPath); + return; } } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 5952cb63717e..92854249c9a6 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -8,6 +8,7 @@ import { PythonInterpreter } from '../../interpreter/contracts'; import { ExecutionInfo, IDisposable, Version } from '../types'; import { Architecture } from '../utils/platform'; import { EnvironmentVariables } from '../variables/types'; +import { CondaExecutionService } from './condaExecutionService'; export const IBufferDecoder = Symbol('IBufferDecoder'); export interface IBufferDecoder { @@ -71,6 +72,7 @@ export type ExecutionFactoryCreateWithEnvironmentOptions = { export interface IPythonExecutionFactory { create(options: ExecutionFactoryCreationOptions): Promise; createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise; + createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise; } export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown'; export type PythonVersionInfo = [number, number, number, ReleaseLevel]; diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 863a433d34ac..25147cf4b894 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,11 +9,9 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IProcessServiceFactory } from '../../common/process/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -24,15 +22,13 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, - @inject(ICondaService) condaService: ICondaService, @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, + @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[] ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService, serviceContainer, processServiceFactory); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index 58ae0abb400f..816b3ea3df6b 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -7,11 +7,9 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; -import { IProcessServiceFactory } from '../../common/process/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() @@ -20,13 +18,11 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(ICondaService) condaService: ICondaService, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IProcessServiceFactory) processServiceFactory: IProcessServiceFactory, + @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, condaService, platformService, serviceContainer, processServiceFactory); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 84d6629e7b71..b56629d2374a 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,12 +9,9 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; -import { CondaExecutionService } from '../../common/process/condaExecutionService'; -import { IProcessService, IProcessServiceFactory, IPythonExecutableInfo } from '../../common/process/types'; +import { IPythonExecutableInfo, IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { ICondaService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionService } from '../../terminals/types'; @injectable() @@ -27,10 +24,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(ICondaService) protected readonly condaService: ICondaService, @inject(IPlatformService) protected readonly platformService: IPlatformService, - @inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer, - @inject(IProcessServiceFactory) protected readonly processServiceFactory: IProcessServiceFactory + @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory ) {} public async executeFile(file: Uri) { @@ -67,13 +62,10 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const pythonSettings = this.configurationService.getSettings(resource); const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; - const condaEnvironment = await this.condaService.getCondaEnvironment(pythonSettings.pythonPath); - if (condaEnvironment) { - const condaFile = await this.condaService.getCondaFile(); - const processService: IProcessService = await this.processServiceFactory.create(resource); - const condaExecutionService = new CondaExecutionService(this.serviceContainer, processService, command, condaFile, condaEnvironment); + const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); + if (condaExecutionService) { return condaExecutionService.getExecutableInfo(command, [...launchArgs, ...args]); } diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 8db786cc36b6..0efc770b5afb 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -9,10 +9,10 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; +import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; -import { ICondaService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; @@ -26,10 +26,9 @@ suite('Terminal - Django Shell Code Execution', () => { let workspace: TypeMoq.IMock; let platform: TypeMoq.IMock; let settings: TypeMoq.IMock; + let pythonExecutionFactory: TypeMoq.IMock; let disposables: Disposable[] = []; - let condaService: TypeMoq.IMock; setup(() => { - condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); const terminalFactory = TypeMoq.Mock.ofType(); terminalSettings = TypeMoq.Mock.ofType(); terminalService = TypeMoq.Mock.ofType(); @@ -46,19 +45,16 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - const serviceContainer = TypeMoq.Mock.ofType(); - const processServiceFactory = TypeMoq.Mock.ofType(); + pythonExecutionFactory = TypeMoq.Mock.ofType(); executor = new DjangoShellCodeExecutionProvider( terminalFactory.object, configService.object, workspace.object, documentManager.object, - condaService.object, platform.object, commandManager.object, fileSystem.object, - serviceContainer.object, - processServiceFactory.object, + pythonExecutionFactory.object, disposables ); @@ -82,14 +78,11 @@ suite('Terminal - Django Shell Code Execution', () => { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(undefined)); const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -181,20 +174,21 @@ suite('Terminal - Django Shell Code Execution', () => { async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }, resource?: Uri) { settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); const hasEnvName = condaEnv.name !== ''; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); - expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); + expect(replCommandArgs.command).to.be.equal(condaFile, 'Incorrect conda path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); } test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 21916afa1499..825b3b9b02cb 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -9,11 +9,11 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; +import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; -import { ICondaService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; @@ -29,7 +29,6 @@ suite('Terminal - Code Execution', () => { let platform: TypeMoq.IMock; let workspaceFolder: TypeMoq.IMock; let settings: TypeMoq.IMock; - let condaService: TypeMoq.IMock; let disposables: Disposable[] = []; let executor: ICodeExecutionService; let expectedTerminalTitle: string | undefined; @@ -37,8 +36,7 @@ suite('Terminal - Code Execution', () => { let documentManager: TypeMoq.IMock; let commandManager: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let processServiceFactory: TypeMoq.IMock; + let pythonExecutionFactory: TypeMoq.IMock; let isDjangoRepl: boolean; teardown(() => { @@ -62,9 +60,7 @@ suite('Terminal - Code Execution', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - condaService = TypeMoq.Mock.ofType(); - serviceContainer = TypeMoq.Mock.ofType(); - processServiceFactory = TypeMoq.Mock.ofType(); + pythonExecutionFactory = TypeMoq.Mock.ofType(); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); @@ -76,24 +72,13 @@ suite('Terminal - Code Execution', () => { configService.object, workspace.object, disposables, - condaService.object, platform.object, - serviceContainer.object, - processServiceFactory.object + pythonExecutionFactory.object ); break; } case 'Repl Execution': { - executor = new ReplProvider( - terminalFactory.object, - configService.object, - workspace.object, - condaService.object, - serviceContainer.object, - processServiceFactory.object, - disposables, - platform.object - ); + executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, pythonExecutionFactory.object, disposables, platform.object); expectedTerminalTitle = 'REPL'; break; } @@ -107,12 +92,10 @@ suite('Terminal - Code Execution', () => { configService.object, workspace.object, documentManager.object, - condaService.object, platform.object, commandManager.object, fileSystem.object, - serviceContainer.object, - processServiceFactory.object, + pythonExecutionFactory.object, disposables ); expectedTerminalTitle = 'Django Shell'; @@ -246,13 +229,13 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); 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())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -280,19 +263,21 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); terminalSettings.setup(t => t.executeInFileDir).returns(() => false); workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); await executor.executeFile(file); const hasEnvName = condaEnv.name !== ''; - const expectedPythonPath = 'conda'; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); - terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(condaFile), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); } test('Ensure conda args with conda env name are sent to terminal if there is a conda environment with a name', async () => { @@ -306,6 +291,7 @@ suite('Terminal - Code Execution', () => { }); async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { + pythonExecutionFactory.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); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); @@ -315,8 +301,7 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.never()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -357,8 +342,12 @@ suite('Terminal - Code Execution', () => { async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }) { settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(pythonPath)).returns(() => Promise.resolve(condaEnv)); + + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); const hasEnvName = condaEnv.name !== ''; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; @@ -370,8 +359,7 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - condaService.verify(async c => c.getCondaEnvironment(pythonPath), TypeMoq.Times.once()); - condaService.verify(async c => c.getCondaFile(), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { From 2ffe1877c780b12491f70ed222622fc728f3ea1b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 7 Nov 2019 15:19:08 -0800 Subject: [PATCH 32/58] getExecutableInfo unit tests + more concise conda getExecutableInfo --- .../common/process/condaExecutionService.ts | 18 +++---------- .../condaExecutionService.unit.test.ts | 27 ++++++------------- .../common/process/pythonProcess.unit.test.ts | 9 +++++++ 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 51b1492d9e4c..7445e5a4924b 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -18,19 +18,9 @@ export class CondaExecutionService extends PythonExecutionService { super(serviceContainer, procService, pythonPath); } - public getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { - if (this.condaEnvironment.name !== '') { - return { - command: this.condaFile, - args: ['run', '-n', this.condaEnvironment.name, 'python', ...args] - }; - } - if (this.condaEnvironment.path.length > 0) { - return { - command: this.condaFile, - args: ['run', '-p', this.condaEnvironment.path, 'python', ...args] - }; - } - return { command, args }; + public getExecutableInfo(_: string, args: string[]): IPythonExecutableInfo { + const executionArgs = this.condaEnvironment.name !== '' ? ['run', '-n', this.condaEnvironment.name] : ['run', '-p', this.condaEnvironment.path]; + + return { command: this.condaFile, args: [...executionArgs, 'python', ...args] }; } } diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts index 1d39e6525b28..1e2fb028fbe1 100644 --- a/src/test/common/process/condaExecutionService.unit.test.ts +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -5,7 +5,6 @@ import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../../client/common/platform/types'; import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; import { IProcessService } from '../../../client/common/process/types'; -import { CondaEnvironmentInfo } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; suite('CondaExecutionService', () => { @@ -25,31 +24,21 @@ suite('CondaExecutionService', () => { serviceContainer.setup(s => s.get(IFileSystem)).returns(() => fileSystem.object); }); - async function testExecutionService(environment: CondaEnvironmentInfo, expectedCommand: string, expectedArgs: string[]): Promise { - const expectedExecResult = { stdout: 'foo' }; - - processService.setup(p => p.exec(TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), {})).returns(() => Promise.resolve(expectedExecResult)); - + test('getExecutableInfo with a named environment should return an executable command using the environment name', () => { + const environment = { name: 'foo', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); - const executableInfo = await executionService.exec(args, {}); - - expect(executableInfo).to.be.equal(expectedExecResult); - processService.verify(p => p.exec(expectedCommand, expectedArgs, {}), TypeMoq.Times.once()); - } + const result = executionService.getExecutableInfo(pythonPath, args); - test('getExecutableInfo with a named environment should return an executable command using the environment name', async () => { - const environment = { name: 'foo', path: 'bar' }; - await testExecutionService(environment, condaFile, ['run', '-n', environment.name, 'python', ...args]); + expect(result).to.deep.equal({ command: condaFile, args: ['run', '-n', environment.name, 'python', ...args] }); }); test('getExecutableInfo with a non-named environment should return an executable command using the environment npathame', async () => { const environment = { name: '', path: 'bar' }; - await testExecutionService(environment, condaFile, ['run', '-p', environment.path, 'python', ...args]); - }); + executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); + + const result = executionService.getExecutableInfo(pythonPath, args); - test('getExecutableInfo with an environment without a name or a prefix should return an executable command using pythonPath', async () => { - const environment = { name: '', path: '' }; - await testExecutionService(environment, pythonPath, args); + expect(result).to.deep.equal({ command: condaFile, args: ['run', '-p', environment.path, 'python', ...args] }); }); }); diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index c1bfce6c9c80..4a7341b1dabf 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -249,4 +249,13 @@ suite('PythonExecutionService', () => { expect(result).to.eventually.be.rejectedWith(`Module '${moduleName}' not installed`); }); + + test('getExecutableInfo should return the command and execution arguments as is', () => { + const args = ['-a', 'b', '-c']; + const command = 'command'; + + const result = executionService.getExecutableInfo(command, args); + + expect(result).to.deep.equal({ command, args }, 'getExecutableInfo should return the command and execution arguments as is'); + }); }); From 1df9f8a08704ff19fe979ef486eb3fff56634811 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 7 Nov 2019 15:31:47 -0800 Subject: [PATCH 33/58] createCondaExecutionService unit tests --- .../pythonExecutionFactory.unit.test.ts | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index a0b7ba8de6ef..b1aa6c4395cc 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -16,13 +16,7 @@ import { ProcessService } from '../../../client/common/process/proc'; import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; import { PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; import { PythonExecutionService } from '../../../client/common/process/pythonProcess'; -import { - ExecutionFactoryCreationOptions, - IBufferDecoder, - IProcessLogger, - IProcessServiceFactory, - IPythonExecutionService -} from '../../../client/common/process/types'; +import { ExecutionFactoryCreationOptions, IBufferDecoder, IProcessLogger, IProcessServiceFactory, IPythonExecutionService } from '../../../client/common/process/types'; import { WindowsStorePythonProcess } from '../../../client/common/process/windowsStorePythonProcess'; import { IConfigurationService, IDisposableRegistry } from '../../../client/common/types'; import { Architecture } from '../../../client/common/utils/platform'; @@ -275,6 +269,29 @@ suite('Process - PythonExecutionFactory', () => { expect(service).instanceOf(PythonExecutionService); assert.equal(createInvoked, false); }); + + test('Ensure `createCondaExecutionService` creates a CondaExecutionService instance if there is a conda environment', async () => { + const pythonPath = 'path/to/python'; + when(condaService.getCondaEnvironment(pythonPath)).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); + when(condaService.getCondaFile()).thenResolve('conda'); + + const result = await factory.createCondaExecutionService(pythonPath, processService, resource); + + expect(result).instanceOf(CondaExecutionService); + verify(condaService.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(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); + + const result = await factory.createCondaExecutionService(pythonPath, processService); + + expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).never(); + }); }); }); }); From faf1f39059e80fd4ad4c746f7eea44cc474fb471 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 7 Nov 2019 15:57:34 -0800 Subject: [PATCH 34/58] Add conda version check --- .../common/process/pythonExecutionFactory.ts | 9 +++++ .../pythonExecutionFactory.unit.test.ts | 40 ++++++++++++++----- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 3fd1e00442c1..0b2cccf6cb66 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import { lt } from 'semver'; import { Uri } from 'vscode'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; @@ -26,6 +27,9 @@ import { } from './types'; import { WindowsStorePythonProcess } from './windowsStorePythonProcess'; +// Minimum version number of conda required to be able to use 'conda run' +export const CONDA_RUN_VERSION = '4.6.0'; + @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { constructor( @@ -74,6 +78,11 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { + const condaVersion = await this.condaService.getCondaVersion(); + if (!condaVersion || lt(condaVersion, CONDA_RUN_VERSION)) { + return; + } + const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); if (condaEnvironment) { const condaFile = await this.condaService.getCondaFile(); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index b1aa6c4395cc..8ee239ff6d9a 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -14,7 +14,7 @@ import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessLogger } from '../../../client/common/process/logger'; import { ProcessService } from '../../../client/common/process/proc'; import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; -import { PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; +import { CONDA_RUN_VERSION, PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; import { PythonExecutionService } from '../../../client/common/process/pythonProcess'; import { ExecutionFactoryCreationOptions, IBufferDecoder, IProcessLogger, IProcessServiceFactory, IPythonExecutionService } from '../../../client/common/process/types'; import { WindowsStorePythonProcess } from '../../../client/common/process/windowsStorePythonProcess'; @@ -173,13 +173,14 @@ suite('Process - PythonExecutionFactory', () => { expect(service).instanceOf(WindowsStorePythonProcess); }); - test('Ensure `create` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + test('Ensure `create` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); when(processFactory.create(resource)).thenResolve(instance(processService)); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); when(condaService.getCondaEnvironment(pythonPath)).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); when(condaService.getCondaFile()).thenResolve('conda'); @@ -187,29 +188,31 @@ suite('Process - PythonExecutionFactory', () => { verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); + verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); verify(condaService.getCondaFile()).once(); expect(service).instanceOf(CondaExecutionService); }); - test('Ensure `create` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + test('Ensure `create` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); when(processFactory.create(resource)).thenResolve(instance(processService)); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); + when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); const service = await factory.create({ resource }); verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); - verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaVersion()).once(); + verify(condaService.getCondaEnvironment(pythonPath)).never(); verify(condaService.getCondaFile()).never(); expect(service).instanceOf(PythonExecutionService); }); - test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if getCondaEnvironment() returns a valid object', async () => { + test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => { let createInvoked = false; const pythonPath = 'path/to/python'; const mockExecService = 'mockService'; @@ -222,6 +225,7 @@ suite('Process - PythonExecutionFactory', () => { when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); when(condaService.getCondaEnvironment(anyString())).thenResolve({ name: 'foo', path: 'path/to/foo/env' }); when(condaService.getCondaFile()).thenResolve('conda'); @@ -240,7 +244,7 @@ suite('Process - PythonExecutionFactory', () => { assert.equal(createInvoked, false); }); - test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if getCondaEnvironment() returns undefined', async () => { + test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => { let createInvoked = false; const pythonPath = 'path/to/python'; const mockExecService = 'mockService'; @@ -253,17 +257,15 @@ suite('Process - PythonExecutionFactory', () => { when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); - when(condaService.getCondaEnvironment(anyString())).thenResolve(undefined); + when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); const service = await factory.createActivatedEnvironment({ resource, interpreter }); verify(condaService.getCondaFile()).never(); verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); + verify(condaService.getCondaVersion()).once(); if (!interpreter) { verify(pythonSettings.pythonPath).once(); - verify(condaService.getCondaEnvironment(pythonPath)).once(); - } else { - verify(condaService.getCondaEnvironment(interpreter.path)).once(); } expect(service).instanceOf(PythonExecutionService); @@ -273,11 +275,13 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure `createCondaExecutionService` creates a CondaExecutionService instance if there is a conda environment', async () => { const pythonPath = 'path/to/python'; when(condaService.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, processService, resource); expect(result).instanceOf(CondaExecutionService); + verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); verify(condaService.getCondaFile()).once(); }); @@ -285,13 +289,27 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure `createCondaExecutionService` returns undefined if there is no conda environment', async () => { const pythonPath = 'path/to/python'; when(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); + when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); const result = await factory.createCondaExecutionService(pythonPath, processService); expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); + verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); verify(condaService.getCondaFile()).never(); }); + + 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')); + + const result = await factory.createCondaExecutionService(pythonPath, processService); + + expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); + verify(condaService.getCondaVersion()).once(); + verify(condaService.getCondaEnvironment(pythonPath)).never(); + verify(condaService.getCondaFile()).never(); + }); }); }); }); From 6a772bf9ed8e249216e8e901ba218e285b2e0ad9 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 7 Nov 2019 16:05:48 -0800 Subject: [PATCH 35/58] Housekeeping --- .../common/process/condaExecutionService.ts | 4 ++-- src/client/common/process/pythonProcess.ts | 18 +++++++------- src/client/common/process/types.ts | 6 ++--- .../codeExecution/djangoShellCodeExecution.ts | 24 +++++++++---------- .../codeExecution/terminalCodeExecution.ts | 10 ++++---- .../condaExecutionService.unit.test.ts | 4 ++-- .../common/process/pythonProcess.unit.test.ts | 2 +- src/test/datascience/executionServiceMock.ts | 2 +- src/test/datascience/mockPythonService.ts | 2 +- 9 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 7445e5a4924b..999e62aa0540 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -4,7 +4,7 @@ import { injectable } from 'inversify'; import { CondaEnvironmentInfo } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { PythonExecutionService } from './pythonProcess'; -import { IProcessService, IPythonExecutableInfo } from './types'; +import { IProcessService, PythonExecutionInfo } from './types'; @injectable() export class CondaExecutionService extends PythonExecutionService { @@ -18,7 +18,7 @@ export class CondaExecutionService extends PythonExecutionService { super(serviceContainer, procService, pythonPath); } - public getExecutableInfo(_: string, args: string[]): IPythonExecutableInfo { + public getExecutionInfo(_: string, args: string[]): PythonExecutionInfo { const executionArgs = this.condaEnvironment.name !== '' ? ['run', '-n', this.condaEnvironment.name] : ['run', '-p', this.condaEnvironment.path]; return { command: this.condaFile, args: [...executionArgs, 'python', ...args] }; diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index a77c32f26e27..d43b7a13f048 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -16,9 +16,9 @@ import { ExecutionResult, InterpreterInfomation, IProcessService, - IPythonExecutableInfo, IPythonExecutionService, ObservableExecutionResult, + PythonExecutionInfo, PythonVersionInfo, SpawnOptions } from './types'; @@ -38,7 +38,7 @@ export class PythonExecutionService implements IPythonExecutionService { // See these two bugs: // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 - const { command, args } = this.getExecutableInfo(this.pythonPath, [file]); + const { command, args } = this.getExecutionInfo(this.pythonPath, [file]); const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000).then(output => output ? output.stdout.trim() : '--timed out--' ); // --timed out-- should cause an exception @@ -69,11 +69,11 @@ export class PythonExecutionService implements IPythonExecutionService { return this.pythonPath; } - const { command, args } = this.getExecutableInfo(this.pythonPath, ['-c', 'import sys;print(sys.executable)']); + const { command, args } = this.getExecutionInfo(this.pythonPath, ['-c', 'import sys;print(sys.executable)']); return this.procService.exec(command, args, { throwOnStdErr: true }).then(output => output.stdout.trim()); } public async isModuleInstalled(moduleName: string): Promise { - const { command, args } = this.getExecutableInfo(this.pythonPath, ['-c', `import ${moduleName}`]); + const { command, args } = this.getExecutionInfo(this.pythonPath, ['-c', `import ${moduleName}`]); return this.procService .exec(command, args, { throwOnStdErr: true }) .then(() => true) @@ -82,22 +82,22 @@ export class PythonExecutionService implements IPythonExecutionService { public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutableInfo(this.pythonPath, args); + const executable = this.getExecutionInfo(this.pythonPath, args); return this.procService.execObservable(executable.command, executable.args, opts); } public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutableInfo(this.pythonPath, ['-m', moduleName, ...args]); + const executable = this.getExecutionInfo(this.pythonPath, ['-m', moduleName, ...args]); return this.procService.execObservable(executable.command, executable.args, opts); } public async exec(args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutableInfo(this.pythonPath, args); + const executable = this.getExecutionInfo(this.pythonPath, args); return this.procService.exec(executable.command, executable.args, opts); } public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutableInfo(this.pythonPath, ['-m', moduleName, ...args]); + const executable = this.getExecutionInfo(this.pythonPath, ['-m', moduleName, ...args]); const result = await this.procService.exec(executable.command, executable.args, opts); // If a module is not installed we'll have something in stderr. @@ -111,7 +111,7 @@ export class PythonExecutionService implements IPythonExecutionService { return result; } - public getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo { + public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { return { command, args }; } } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 92854249c9a6..1298f321f995 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -90,7 +90,7 @@ export interface IPythonExecutionService { getInterpreterInformation(): Promise; getExecutablePath(): Promise; isModuleInstalled(moduleName: string): Promise; - getExecutableInfo(command: string, args: string[]): IPythonExecutableInfo; + getExecutionInfo(command: string, args: string[]): PythonExecutionInfo; execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult; execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult; @@ -99,10 +99,10 @@ export interface IPythonExecutionService { execModule(moduleName: string, args: string[], options: SpawnOptions): Promise>; } -export interface IPythonExecutableInfo { +export type PythonExecutionInfo = { command: string; args: string[]; -} +}; export class StdErrError extends Error { constructor(message: string) { diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 25147cf4b894..e1b7d23d7bab 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,7 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory } from '../../common/process/types'; +import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { DjangoContextInitializer } from './djangoContext'; @@ -33,22 +33,22 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise<{ command: string; args: string[] }> { - // We need the executable info but not the 'manage.py shell' args - const { command, args } = await super.getExecutableInfo(resource); - return { command, args: args.concat(executeArgs) }; - } - - public async getExecutableInfo(resource?: Uri): Promise<{ command: string; args: string[] }> { - const { command, args } = await super.getExecutableInfo(resource); + public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { + const { command, args: executableArgs } = await super.getExecutableInfo(resource, args); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace; const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py'); - args.push(managePyPath.fileToCommandArgument()); - args.push('shell'); - return { command, args }; + executableArgs.push(managePyPath.fileToCommandArgument()); + executableArgs.push('shell'); + return { command, args: executableArgs }; + } + + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { + // We need the executable info but not the 'manage.py shell' args + const { command, args } = await super.getExecutableInfo(resource); + return { command, args: args.concat(executeArgs) }; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index b56629d2374a..8517264c3944 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,7 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutableInfo, IPythonExecutionFactory } from '../../common/process/types'; +import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICodeExecutionService } from '../../terminals/types'; @@ -58,7 +58,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.replActive; } - public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { + + public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { const pythonSettings = this.configurationService.getSettings(resource); const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; @@ -66,7 +67,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); if (condaExecutionService) { - return condaExecutionService.getExecutableInfo(command, [...launchArgs, ...args]); + return condaExecutionService.getExecutionInfo(command, [...launchArgs, ...args]); } const isWindows = this.platformService.isWindows; @@ -77,7 +78,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { }; } - public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { + // Overridden in subclasses, see djangoShellCodeExecution.ts + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { return this.getExecutableInfo(resource, executeArgs); } private getTerminalService(resource?: Uri): ITerminalService { diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts index 1e2fb028fbe1..4b5836e813ab 100644 --- a/src/test/common/process/condaExecutionService.unit.test.ts +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -28,7 +28,7 @@ suite('CondaExecutionService', () => { const environment = { name: 'foo', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); - const result = executionService.getExecutableInfo(pythonPath, args); + const result = executionService.getExecutionInfo(pythonPath, args); expect(result).to.deep.equal({ command: condaFile, args: ['run', '-n', environment.name, 'python', ...args] }); }); @@ -37,7 +37,7 @@ suite('CondaExecutionService', () => { const environment = { name: '', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); - const result = executionService.getExecutableInfo(pythonPath, args); + const result = executionService.getExecutionInfo(pythonPath, args); expect(result).to.deep.equal({ command: condaFile, args: ['run', '-p', environment.path, 'python', ...args] }); }); diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index 4a7341b1dabf..46d139693e45 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -254,7 +254,7 @@ suite('PythonExecutionService', () => { const args = ['-a', 'b', '-c']; const command = 'command'; - const result = executionService.getExecutableInfo(command, args); + const result = executionService.getExecutionInfo(command, args); expect(result).to.deep.equal({ command, args }, 'getExecutableInfo should return the command and execution arguments as is'); }); diff --git a/src/test/datascience/executionServiceMock.ts b/src/test/datascience/executionServiceMock.ts index 15d9f01ebacd..baadcc685d71 100644 --- a/src/test/datascience/executionServiceMock.ts +++ b/src/test/datascience/executionServiceMock.ts @@ -67,7 +67,7 @@ export class MockPythonExecutionService implements IPythonExecutionService { return result; } - public getExecutableInfo(command: string, args: string[]) { + public getExecutionInfo(command: string, args: string[]) { return { command, args }; } } diff --git a/src/test/datascience/mockPythonService.ts b/src/test/datascience/mockPythonService.ts index 4c25dd44e21f..c69a8e68e858 100644 --- a/src/test/datascience/mockPythonService.ts +++ b/src/test/datascience/mockPythonService.ts @@ -65,7 +65,7 @@ export class MockPythonService implements IPythonExecutionService { this.procService.setDelay(timeout); } - public getExecutableInfo(command: string, args: string[]) { + public getExecutionInfo(command: string, args: string[]) { return { command, args }; } } From 689bf1f9326b051202d5a29293401480baea956b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 08:53:32 -0800 Subject: [PATCH 36/58] Move terminal changes to another PR --- .../codeExecution/djangoShellCodeExecution.ts | 30 ++--- src/client/terminals/codeExecution/repl.ts | 5 +- .../codeExecution/terminalCodeExecution.ts | 45 ++----- .../djangoShellCodeExect.unit.test.ts | 80 +++--------- .../terminalCodeExec.unit.test.ts | 121 +++--------------- 5 files changed, 59 insertions(+), 222 deletions(-) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index e1b7d23d7bab..3066ec27fb71 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,7 +9,6 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { DjangoContextInitializer } from './djangoContext'; @@ -17,38 +16,31 @@ import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvider { - constructor( - @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, + constructor(@inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, - @inject(IDisposableRegistry) disposableRegistry: Disposable[] - ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); + @inject(IDisposableRegistry) disposableRegistry: Disposable[]) { + + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - - public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { - const { command, args: executableArgs } = await super.getExecutableInfo(resource, args); + public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { + const pythonSettings = this.configurationService.getSettings(resource); + const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; + const args = pythonSettings.terminal.launchArgs.slice(); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace; const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py'); - executableArgs.push(managePyPath.fileToCommandArgument()); - executableArgs.push('shell'); - return { command, args: executableArgs }; - } - - public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { - // We need the executable info but not the 'manage.py shell' args - const { command, args } = await super.getExecutableInfo(resource); - return { command, args: args.concat(executeArgs) }; + args.push(managePyPath.fileToCommandArgument()); + args.push('shell'); + return { command, args }; } } diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index 816b3ea3df6b..c40cc3792995 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -7,7 +7,6 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -18,11 +17,11 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); + + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 8517264c3944..c80089d196f4 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,7 +9,6 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICodeExecutionService } from '../../terminals/types'; @@ -19,20 +18,22 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { protected terminalTitle!: string; private _terminalService!: ITerminalService; private replActive?: Promise; - constructor( - @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, + constructor(@inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IPlatformService) protected readonly platformService: IPlatformService, - @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory - ) {} + @inject(IPlatformService) protected readonly platformService: IPlatformService) { + } public async executeFile(file: Uri) { + const pythonSettings = this.configurationService.getSettings(file); + await this.setCwdForFileExecution(file); - const { command, args } = await this.getExecuteFileArgs(file, [file.fsPath.fileToCommandArgument()]); - await this.getTerminalService(file).sendCommand(command, args); + const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; + const launchArgs = pythonSettings.terminal.launchArgs; + + await this.getTerminalService(file).sendCommand(command, launchArgs.concat(file.fsPath.fileToCommandArgument())); } public async execute(code: string, resource?: Uri): Promise { @@ -49,7 +50,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { return; } this.replActive = new Promise(async resolve => { - const replCommandArgs = await this.getExecutableInfo(resource); + const replCommandArgs = this.getReplCommandArgs(resource); await this.getTerminalService(resource).sendCommand(replCommandArgs.command, replCommandArgs.args); // Give python repl time to start before we start sending text. @@ -58,29 +59,11 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.replActive; } - - public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { + public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { const pythonSettings = this.configurationService.getSettings(resource); - const command = pythonSettings.pythonPath; - const launchArgs = pythonSettings.terminal.launchArgs; - - const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); - - if (condaExecutionService) { - return condaExecutionService.getExecutionInfo(command, [...launchArgs, ...args]); - } - - const isWindows = this.platformService.isWindows; - - return { - command: isWindows ? command.replace(/\\/g, '/') : command, - args: [...launchArgs, ...args] - }; - } - - // Overridden in subclasses, see djangoShellCodeExecution.ts - public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { - return this.getExecutableInfo(resource, executeArgs); + const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; + const args = pythonSettings.terminal.launchArgs.slice(); + return { command, args }; } private getTerminalService(resource?: Uri): ITerminalService { if (!this._terminalService) { diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 0efc770b5afb..e85da9496e88 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -9,11 +9,8 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; -import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; -import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; @@ -26,7 +23,6 @@ suite('Terminal - Django Shell Code Execution', () => { let workspace: TypeMoq.IMock; let platform: TypeMoq.IMock; let settings: TypeMoq.IMock; - let pythonExecutionFactory: TypeMoq.IMock; let disposables: Disposable[] = []; setup(() => { const terminalFactory = TypeMoq.Mock.ofType(); @@ -34,9 +30,7 @@ suite('Terminal - Django Shell Code Execution', () => { terminalService = TypeMoq.Mock.ofType(); const configService = TypeMoq.Mock.ofType(); workspace = TypeMoq.Mock.ofType(); - workspace - .setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => { + workspace.setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: () => void 0 }; @@ -45,18 +39,8 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - pythonExecutionFactory = TypeMoq.Mock.ofType(); - executor = new DjangoShellCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - documentManager.object, - platform.object, - commandManager.object, - fileSystem.object, - pythonExecutionFactory.object, - disposables - ); + executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, + workspace.object, documentManager.object, platform.object, commandManager.object, fileSystem.object, disposables); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); @@ -74,12 +58,13 @@ suite('Terminal - Django Shell Code Execution', () => { disposables = []; }); - async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { + function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, + terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); + const replCommandArgs = (executor as DjangoShellCodeExecutionProvider).getReplCommandArgs(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); @@ -90,7 +75,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); + testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { @@ -98,7 +83,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, when building repl args on Windows', async () => { @@ -106,7 +91,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, on non Windows', async () => { @@ -114,7 +99,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, on non Windows', async () => { @@ -122,7 +107,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure current workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -133,7 +118,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure current workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -144,7 +129,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -156,7 +141,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -168,42 +153,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); - async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }, resource?: Uri) { - settings.setup(s => s.pythonPath).returns(() => pythonPath); - terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - - const condaFile = 'conda'; - const serviceContainer = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; - const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); - - const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); - - expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); - expect(replCommandArgs.command).to.be.equal(condaFile, 'Incorrect conda path'); - expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - } - - test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { - const pythonPath = 'c:/program files/python/python.exe'; - const condaPath = { name: 'foo-env', path: 'path/to/foo-env' }; - const terminalArgs = ['-a', 'b', '-c']; - - await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); - }); - - test('Ensure conda args including env path are passed when using a conda environment with an empty name', async () => { - const pythonPath = 'c:/program files/python/python.exe'; - const condaPath = { name: '', path: 'path/to/foo-env' }; - const terminalArgs = ['-a', 'b', '-c']; - - await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); - }); }); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 825b3b9b02cb..35bffee23ddb 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -9,12 +9,9 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; -import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; -import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; @@ -36,7 +33,6 @@ suite('Terminal - Code Execution', () => { let documentManager: TypeMoq.IMock; let commandManager: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; - let pythonExecutionFactory: TypeMoq.IMock; let isDjangoRepl: boolean; teardown(() => { @@ -60,25 +56,18 @@ suite('Terminal - Code Execution', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - pythonExecutionFactory = TypeMoq.Mock.ofType(); + settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - disposables, - platform.object, - pythonExecutionFactory.object - ); + executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); break; } case 'Repl Execution': { - executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, pythonExecutionFactory.object, disposables, platform.object); + executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); expectedTerminalTitle = 'REPL'; break; } @@ -87,17 +76,8 @@ suite('Terminal - Code Execution', () => { workspace.setup(w => w.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: noop }; }); - executor = new DjangoShellCodeExecutionProvider( - terminalFactory.object, - configService.object, - workspace.object, - documentManager.object, - platform.object, - commandManager.object, - fileSystem.object, - pythonExecutionFactory.object, - disposables - ); + executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, + platform.object, commandManager.object, fileSystem.object, disposables); expectedTerminalTitle = 'Django Shell'; break; } @@ -229,13 +209,11 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); 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())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -258,116 +236,51 @@ suite('Terminal - Code Execution', () => { await testFileExecution(false, PYTHON_PATH, ['-a', '-b', '-c'], file); }); - async function testCondaFileExecution(pythonPath: string, terminalArgs: string[], file: Uri, condaEnv: { name: string; path: string }): Promise { - settings.setup(s => s.pythonPath).returns(() => pythonPath); - terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - terminalSettings.setup(t => t.executeInFileDir).returns(() => false); - workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); - - const condaFile = 'conda'; - const serviceContainer = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); - - await executor.executeFile(file); - - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; - const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; - - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); - terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(condaFile), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); - } - - test('Ensure conda args with conda env name are sent to terminal if there is a conda environment with a name', async () => { - const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); - await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: 'foo-env', path: 'path/to/foo-env' }); - }); - - test('Ensure conda args with conda env path are sent to terminal if there is a conda environment without a name', async () => { - const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); - await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: '', path: 'path/to/foo-env' }); - }); - - async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; - const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); + const replCommandArgs = (executor as TerminalCodeExecutionProvider).getReplCommandArgs(); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } - test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { + test('Ensure fully qualified python path is escaped when building repl args on Windows', () => { const pythonPath = 'c:\\program files\\python\\python.exe'; const terminalArgs = ['-a', 'b', 'c']; - await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); + testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); }); - test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { + test('Ensure fully qualified python path is returned as is, when building repl args on Windows', () => { const pythonPath = 'c:/program files/python/python.exe'; const terminalArgs = ['-a', 'b', 'c']; - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, when building repl args on Windows', async () => { + test('Ensure python path is returned as is, when building repl args on Windows', () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure fully qualified python path is returned as is, on non Windows', async () => { + test('Ensure fully qualified python path is returned as is, on non Windows', () => { const pythonPath = 'usr/bin/python'; const terminalArgs = ['-a', 'b', 'c']; - await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, on non Windows', async () => { + test('Ensure python path is returned as is, on non Windows', () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); - }); - - async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }) { - settings.setup(s => s.pythonPath).returns(() => pythonPath); - terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - - const condaFile = 'conda'; - const serviceContainer = TypeMoq.Mock.ofType(); - const processService = TypeMoq.Mock.ofType(); - const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); - - const hasEnvName = condaEnv.name !== ''; - const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; - const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : []; - const expectedTerminalArgs = [...condaArgs, ...terminalArgs, ...djangoArgs]; - - const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); - - expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); - expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); - expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); - } - - test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { - await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: 'foo-env', path: 'path/to/foo-env' }); - }); - - test('Ensure conda args with env path are returned when building repl args with a conda env without a name', async () => { - await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: '', path: 'path/to/foo-env' }); + testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); test('Ensure nothing happens when blank text is sent to the terminal', async () => { From 42ba9ffa6d555d6132d3ad6748297376099174d5 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 09:09:14 -0800 Subject: [PATCH 37/58] Typo --- src/test/common/process/condaExecutionService.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts index 4b5836e813ab..37f6c612654f 100644 --- a/src/test/common/process/condaExecutionService.unit.test.ts +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -33,7 +33,7 @@ suite('CondaExecutionService', () => { expect(result).to.deep.equal({ command: condaFile, args: ['run', '-n', environment.name, 'python', ...args] }); }); - test('getExecutableInfo with a non-named environment should return an executable command using the environment npathame', async () => { + test('getExecutableInfo with a non-named environment should return an executable command using the environment path', async () => { const environment = { name: '', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); From 923dea2780e879d4c9dbf17655c2efa7d611a841 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 10:38:10 -0800 Subject: [PATCH 38/58] Revert execObservable and execModuleObservable --- src/client/common/process/pythonProcess.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index d43b7a13f048..c5e4cec946c7 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -82,13 +82,15 @@ export class PythonExecutionService implements IPythonExecutionService { public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutionInfo(this.pythonPath, args); - return this.procService.execObservable(executable.command, executable.args, opts); + // Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output. + // See https://github.com/conda/conda/issues/9412 + return this.procService.execObservable(this.pythonPath, args, opts); } public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutionInfo(this.pythonPath, ['-m', moduleName, ...args]); - return this.procService.execObservable(executable.command, executable.args, opts); + // Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output. + // See https://github.com/conda/conda/issues/9412 + return this.procService.execObservable(this.pythonPath, ['-m', moduleName, ...args], opts); } public async exec(args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; From 77fac1f1e27d2acc6559f9a8dda6749c1a991032 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 12:02:16 -0800 Subject: [PATCH 39/58] Update GH link --- src/client/common/process/pythonProcess.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index e5ed7e4241f7..d197ad6e5f5e 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -50,14 +50,14 @@ export class PythonExecutionService implements IPythonExecutionService { traceError(`Failed to parse interpreter information for '${this.pythonPath}' with JSON ${jsonValue}`, ex); return; } - const versionValue = json.versionInfo.length === 4 ? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}` : json.versionInfo.join('.'); - return { - architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, - path: this.pythonPath, - version: parsePythonVersion(versionValue), - sysVersion: json.sysVersion, - sysPrefix: json.sysPrefix - }; + const versionValue = json.versionInfo.length === 4 ? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}` : json.versionInfo.join('.'); + return { + architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, + path: this.pythonPath, + version: parsePythonVersion(versionValue), + sysVersion: json.sysVersion, + sysPrefix: json.sysPrefix + }; } catch (ex) { traceError(`Failed to get interpreter information for '${this.pythonPath}'`, ex); } @@ -83,13 +83,13 @@ export class PythonExecutionService implements IPythonExecutionService { public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; // Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output. - // See https://github.com/conda/conda/issues/9412 + // See https://github.com/microsoft/vscode-python/issues/8473 return this.procService.execObservable(this.pythonPath, args, opts); } public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; // Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output. - // See https://github.com/conda/conda/issues/9412 + // See https://github.com/microsoft/vscode-python/issues/8473 return this.procService.execObservable(this.pythonPath, ['-m', moduleName, ...args], opts); } public async exec(args: string[], options: SpawnOptions): Promise> { From fd4ddd69bf892ac7beffdd2316f9084e2a5f81f2 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 12:34:37 -0800 Subject: [PATCH 40/58] More housekeeping --- .../common/process/condaExecutionService.ts | 4 ++-- .../common/process/pythonExecutionFactory.ts | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index 999e62aa0540..fd3e008e4de7 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -19,8 +19,8 @@ export class CondaExecutionService extends PythonExecutionService { } public getExecutionInfo(_: string, args: string[]): PythonExecutionInfo { - const executionArgs = this.condaEnvironment.name !== '' ? ['run', '-n', this.condaEnvironment.name] : ['run', '-p', this.condaEnvironment.path]; + const executionArgs = this.condaEnvironment.name !== '' ? ['-n', this.condaEnvironment.name] : ['-p', this.condaEnvironment.path]; - return { command: this.condaFile, args: [...executionArgs, 'python', ...args] }; + return { command: this.condaFile, args: ['run', ...executionArgs, 'python', ...args] }; } } diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index f9184b67b20b..2bf92b94b39b 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -140,15 +140,14 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { return; } - const condaEnvironment = await this.condaService.getCondaEnvironment(pythonPath); + const processServicePromise = processService ? Promise.resolve(processService) : this.processServiceFactory.create(resource); + const [condaEnvironment, condaFile, procService] = await Promise.all([ + this.condaService.getCondaEnvironment(pythonPath), + this.condaService.getCondaFile(), + processServicePromise + ]); if (condaEnvironment) { - const condaFile = await this.condaService.getCondaFile(); - - if (!processService) { - processService = await this.processServiceFactory.create(resource); - } - - return new CondaExecutionService(this.serviceContainer, processService, pythonPath, condaFile, condaEnvironment); + return new CondaExecutionService(this.serviceContainer, procService, pythonPath, condaFile, condaEnvironment); } return; From 6edb1da2fe8d75881f46f2fc8c888837613dde4c Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 12:52:00 -0800 Subject: [PATCH 41/58] Fix compilation errors --- src/client/common/process/pythonDaemon.ts | 4 ++++ src/client/common/process/pythonExecutionFactory.ts | 2 +- src/client/common/process/pythonProcess.ts | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/client/common/process/pythonDaemon.ts b/src/client/common/process/pythonDaemon.ts index 159d21b18a2e..449223c80dca 100644 --- a/src/client/common/process/pythonDaemon.ts +++ b/src/client/common/process/pythonDaemon.ts @@ -20,6 +20,7 @@ import { IPythonExecutionService, ObservableExecutionResult, Output, + PythonExecutionInfo, PythonVersionInfo, SpawnOptions, StdErrError @@ -80,6 +81,9 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi return this.pythonExecutionService.getExecutablePath(); } } + public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { + return { command, args }; + } public async isModuleInstalled(moduleName: string): Promise { this.throwIfRPCConnectionIsDead(); try { diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 2bf92b94b39b..010c63e795b4 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -146,7 +146,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { this.condaService.getCondaFile(), processServicePromise ]); - if (condaEnvironment) { + if (condaEnvironment && condaFile && procService) { return new CondaExecutionService(this.serviceContainer, procService, pythonPath, condaFile, condaEnvironment); } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index d197ad6e5f5e..484fbaa1ec4a 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -80,6 +80,10 @@ export class PythonExecutionService implements IPythonExecutionService { .catch(() => false); } + public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { + return { command, args }; + } + public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; // Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output. @@ -112,8 +116,4 @@ export class PythonExecutionService implements IPythonExecutionService { return result; } - - public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { - return { command, args }; - } } From cf3ba571437fcdb194c470c0f74e2b7ab0308f5e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 12:55:58 -0800 Subject: [PATCH 42/58] Undo formatting changes --- src/client/common/process/pythonProcess.ts | 16 ++++++++-------- src/client/common/process/types.ts | 5 +++-- .../process/pythonExecutionFactory.unit.test.ts | 8 +++++++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 484fbaa1ec4a..11fa92626f55 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -50,14 +50,14 @@ export class PythonExecutionService implements IPythonExecutionService { traceError(`Failed to parse interpreter information for '${this.pythonPath}' with JSON ${jsonValue}`, ex); return; } - const versionValue = json.versionInfo.length === 4 ? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}` : json.versionInfo.join('.'); - return { - architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, - path: this.pythonPath, - version: parsePythonVersion(versionValue), - sysVersion: json.sysVersion, - sysPrefix: json.sysPrefix - }; + const versionValue = json.versionInfo.length === 4 ? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}` : json.versionInfo.join('.'); + return { + architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, + path: this.pythonPath, + version: parsePythonVersion(versionValue), + sysVersion: json.sysVersion, + sysPrefix: json.sysPrefix + }; } catch (ex) { traceError(`Failed to get interpreter information for '${this.pythonPath}'`, ex); } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 152e7d4ca9ed..bbad8643c382 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -93,7 +93,7 @@ export interface IPythonExecutionFactory { * @returns {(Promise)} * @memberof IPythonExecutionFactory */ - createDaemon(options: DaemonExecutionFactoryCreationOptions): Promise; + createDaemon(options: DaemonExecutionFactoryCreationOptions): Promise; createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise; createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise; } @@ -135,7 +135,8 @@ export type PythonExecutionInfo = { * @extends {IPythonExecutionService} * @extends {IDisposable} */ -export interface IPythonDaemonExecutionService extends IPythonExecutionService, IDisposable {} +export interface IPythonDaemonExecutionService extends IPythonExecutionService, IDisposable { +} export class StdErrError extends Error { constructor(message: string) { diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 8ee239ff6d9a..7467cf33791f 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -16,7 +16,13 @@ import { ProcessService } from '../../../client/common/process/proc'; import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; import { CONDA_RUN_VERSION, PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; import { PythonExecutionService } from '../../../client/common/process/pythonProcess'; -import { ExecutionFactoryCreationOptions, IBufferDecoder, IProcessLogger, IProcessServiceFactory, IPythonExecutionService } from '../../../client/common/process/types'; +import { + ExecutionFactoryCreationOptions, + IBufferDecoder, + IProcessLogger, + IProcessServiceFactory, + IPythonExecutionService +} from '../../../client/common/process/types'; import { WindowsStorePythonProcess } from '../../../client/common/process/windowsStorePythonProcess'; import { IConfigurationService, IDisposableRegistry } from '../../../client/common/types'; import { Architecture } from '../../../client/common/utils/platform'; From 5fb07a4cdf4d3025060b222e4c48dd63fe2df965 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 8 Nov 2019 12:57:57 -0800 Subject: [PATCH 43/58] More formatting reverting --- src/client/common/process/pythonProcess.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 11fa92626f55..bdf6220d97f3 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -27,7 +27,11 @@ import { export class PythonExecutionService implements IPythonExecutionService { private readonly fileSystem: IFileSystem; - constructor(serviceContainer: IServiceContainer, private readonly procService: IProcessService, protected readonly pythonPath: string) { + constructor( + serviceContainer: IServiceContainer, + private readonly procService: IProcessService, + protected readonly pythonPath: string + ) { this.fileSystem = serviceContainer.get(IFileSystem); } From 55f96e9fa9d0544d6ed7b23426ed36c9cdc8f6d4 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 08:59:03 -0800 Subject: [PATCH 44/58] Try creating a conda environment first --- src/client/common/process/pythonExecutionFactory.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index b69a7e529735..34b3fb17e565 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -118,23 +118,23 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { } } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { + const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; + const condaExecutionService = await this.createCondaExecutionService(pythonPath); + if (condaExecutionService) { + return condaExecutionService; + } + const envVars = await this.activationHelper.getActivatedEnvironmentVariables(options.resource, options.interpreter, options.allowEnvironmentFetchExceptions); const hasEnvVars = envVars && Object.keys(envVars).length > 0; sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, undefined, { hasEnvVars }); if (!hasEnvVars) { return this.create({ resource: options.resource, pythonPath: options.interpreter ? options.interpreter.path : undefined }); } - const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; const processService: IProcessService = new ProcessService(this.decoder, { ...envVars }); const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); this.serviceContainer.get(IDisposableRegistry).push(processService); - const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); - if (condaExecutionService) { - return condaExecutionService; - } - return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { From 77d94a1cfd77ac841a1d7537d9413c4e553cbf2e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 11:31:05 -0800 Subject: [PATCH 45/58] Fix tests --- .../common/process/pythonExecutionFactory.ts | 8 +- .../condaExecutionService.unit.test.ts | 4 +- .../pythonExecutionFactory.unit.test.ts | 76 +++++++++++++------ 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 34b3fb17e565..a64f8156d5de 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -119,7 +119,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; - const condaExecutionService = await this.createCondaExecutionService(pythonPath); + const condaExecutionService = await this.createCondaExecutionService(pythonPath, undefined, options.resource); if (condaExecutionService) { return condaExecutionService; } @@ -150,6 +150,12 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { processServicePromise ]); if (condaEnvironment && condaFile && procService) { + // Add logging to the newly created process service + if (!processService) { + const processLogger = this.serviceContainer.get(IProcessLogger); + procService.on('exec', processLogger.logProcess.bind(processLogger)); + this.serviceContainer.get(IDisposableRegistry).push(procService); + } return new CondaExecutionService(this.serviceContainer, procService, pythonPath, condaFile, condaEnvironment); } diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts index 37f6c612654f..25ff3c595222 100644 --- a/src/test/common/process/condaExecutionService.unit.test.ts +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -24,7 +24,7 @@ suite('CondaExecutionService', () => { serviceContainer.setup(s => s.get(IFileSystem)).returns(() => fileSystem.object); }); - test('getExecutableInfo with a named environment should return an executable command using the environment name', () => { + test('getExecutionInfo with a named environment should return an executable command using the environment name', () => { const environment = { name: 'foo', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); @@ -33,7 +33,7 @@ suite('CondaExecutionService', () => { expect(result).to.deep.equal({ command: condaFile, args: ['run', '-n', environment.name, 'python', ...args] }); }); - test('getExecutableInfo with a non-named environment should return an executable command using the environment path', async () => { + test('getExecutionInfo with a non-named environment should return an executable command using the environment path', async () => { const environment = { name: '', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 7467cf33791f..06eb3e802617 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { expect } from 'chai'; import { SemVer } from 'semver'; import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; +import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; @@ -12,7 +13,6 @@ import { ConfigurationService } from '../../../client/common/configuration/servi import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessLogger } from '../../../client/common/process/logger'; -import { ProcessService } from '../../../client/common/process/proc'; import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; import { CONDA_RUN_VERSION, PythonExecutionFactory } from '../../../client/common/process/pythonExecutionFactory'; import { PythonExecutionService } from '../../../client/common/process/pythonProcess'; @@ -20,6 +20,7 @@ import { ExecutionFactoryCreationOptions, IBufferDecoder, IProcessLogger, + IProcessService, IProcessServiceFactory, IPythonExecutionService } from '../../../client/common/process/types'; @@ -76,7 +77,7 @@ suite('Process - PythonExecutionFactory', () => { let configService: IConfigurationService; let condaService: ICondaService; let processLogger: IProcessLogger; - let processService: ProcessService; + let processService: typemoq.IMock; let windowsStoreInterpreter: IWindowsStoreInterpreter; setup(() => { bufferDecoder = mock(BufferDecoder); @@ -87,8 +88,15 @@ suite('Process - PythonExecutionFactory', () => { processLogger = mock(ProcessLogger); windowsStoreInterpreter = mock(WindowsStoreInterpreter); when(processLogger.logProcess('', [], {})).thenReturn(); - processService = mock(ProcessService); - when(processService.on('exec', () => { return; })).thenReturn(processService); + processService = typemoq.Mock.ofType(); + processService + .setup(p => + p.on('exec', () => { + return; + }) + ) + .returns(() => processService.object); + processService.setup((p: any) => p.then).returns(() => undefined); const serviceContainer = mock(ServiceContainer); when(serviceContainer.get(IDisposableRegistry)).thenReturn([]); when(serviceContainer.get(IProcessLogger)).thenReturn(processLogger); @@ -105,7 +113,7 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure PythonExecutionService is created', async () => { const pythonSettings = mock(PythonSettings); - when(processFactory.create(resource)).thenResolve(instance(processService)); + when(processFactory.create(resource)).thenResolve(processService.object); when(activationHelper.getActivatedEnvironmentVariables(resource)).thenResolve({ x: '1' }); when(pythonSettings.pythonPath).thenReturn('HELLO'); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); @@ -117,11 +125,18 @@ suite('Process - PythonExecutionFactory', () => { expect(service).instanceOf(PythonExecutionService); }); test('Ensure we use an existing `create` method if there are no environment variables for the activated env', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + + when(processFactory.create(resource)).thenResolve(processService.object); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + let createInvoked = false; const mockExecService = 'something'; factory.create = async (_options: ExecutionFactoryCreationOptions) => { createInvoked = true; - return Promise.resolve(mockExecService as any as IPythonExecutionService); + return Promise.resolve((mockExecService as any) as IPythonExecutionService); }; const service = await verifyCreateActivated(factory, activationHelper, resource, interpreter); @@ -129,6 +144,13 @@ suite('Process - PythonExecutionFactory', () => { assert.equal(createInvoked, true); }); test('Ensure we use an existing `create` method if there are no environment variables (0 length) for the activated env', async () => { + const pythonPath = 'path/to/python'; + const pythonSettings = mock(PythonSettings); + + when(processFactory.create(resource)).thenResolve(processService.object); + when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); + let createInvoked = false; const mockExecService = 'something'; factory.create = async (_options: ExecutionFactoryCreationOptions) => { @@ -166,7 +188,7 @@ suite('Process - PythonExecutionFactory', () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); - when(processFactory.create(resource)).thenResolve(instance(processService)); + when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); when(windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)).thenReturn(true); @@ -183,7 +205,7 @@ suite('Process - PythonExecutionFactory', () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); - when(processFactory.create(resource)).thenResolve(instance(processService)); + 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)); @@ -203,7 +225,7 @@ suite('Process - PythonExecutionFactory', () => { test('Ensure `create` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); - when(processFactory.create(resource)).thenResolve(instance(processService)); + 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')); @@ -219,16 +241,10 @@ suite('Process - PythonExecutionFactory', () => { }); test('Ensure `createActivatedEnvironment` returns a CondaExecutionService instance if createCondaExecutionService() returns a valid object', async () => { - let createInvoked = false; const pythonPath = 'path/to/python'; - const mockExecService = 'mockService'; - factory.create = async (_options: ExecutionFactoryCreationOptions) => { - createInvoked = true; - return Promise.resolve((mockExecService as any) as IPythonExecutionService); - }; - const pythonSettings = mock(PythonSettings); - when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); + + 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)); @@ -238,7 +254,6 @@ suite('Process - PythonExecutionFactory', () => { const service = await factory.createActivatedEnvironment({ resource, interpreter }); verify(condaService.getCondaFile()).once(); - verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); if (!interpreter) { verify(pythonSettings.pythonPath).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); @@ -247,7 +262,6 @@ suite('Process - PythonExecutionFactory', () => { } expect(service).instanceOf(CondaExecutionService); - assert.equal(createInvoked, false); }); test('Ensure `createActivatedEnvironment` returns a PythonExecutionService instance if createCondaExecutionService() returns undefined', async () => { @@ -284,9 +298,25 @@ suite('Process - PythonExecutionFactory', () => { when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); when(condaService.getCondaFile()).thenResolve('conda'); - const result = await factory.createCondaExecutionService(pythonPath, processService, resource); + const result = await factory.createCondaExecutionService(pythonPath, processService.object, resource); + + expect(result).instanceOf(CondaExecutionService); + verify(condaService.getCondaVersion()).once(); + verify(condaService.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(condaService.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); expect(result).instanceOf(CondaExecutionService); + verify(processFactory.create(resource)).once(); verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); verify(condaService.getCondaFile()).once(); @@ -297,19 +327,19 @@ suite('Process - PythonExecutionFactory', () => { when(condaService.getCondaEnvironment(pythonPath)).thenResolve(undefined); when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); - const result = await factory.createCondaExecutionService(pythonPath, processService); + const result = await factory.createCondaExecutionService(pythonPath, processService.object); expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).once(); - verify(condaService.getCondaFile()).never(); + 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')); - const result = await factory.createCondaExecutionService(pythonPath, processService); + const result = await factory.createCondaExecutionService(pythonPath, processService.object); expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); verify(condaService.getCondaVersion()).once(); From 68216fab3b1c0a43df981c30a24bb243abc8050d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 11:44:33 -0800 Subject: [PATCH 46/58] Drop unnecessary command arg in getExecutionInfo --- src/client/common/process/condaExecutionService.ts | 2 +- src/client/common/process/pythonDaemon.ts | 4 ++-- src/client/common/process/pythonProcess.ts | 14 +++++++------- src/client/common/process/types.ts | 2 +- .../process/condaExecutionService.unit.test.ts | 8 ++++---- src/test/common/process/pythonProcess.unit.test.ts | 7 +++---- src/test/datascience/executionServiceMock.ts | 4 ++-- src/test/datascience/mockPythonService.ts | 4 ++-- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/client/common/process/condaExecutionService.ts b/src/client/common/process/condaExecutionService.ts index fd3e008e4de7..c0554161e2e4 100644 --- a/src/client/common/process/condaExecutionService.ts +++ b/src/client/common/process/condaExecutionService.ts @@ -18,7 +18,7 @@ export class CondaExecutionService extends PythonExecutionService { super(serviceContainer, procService, pythonPath); } - public getExecutionInfo(_: string, args: string[]): PythonExecutionInfo { + public getExecutionInfo(args: string[]): PythonExecutionInfo { const executionArgs = this.condaEnvironment.name !== '' ? ['-n', this.condaEnvironment.name] : ['-p', this.condaEnvironment.path]; return { command: this.condaFile, args: ['run', ...executionArgs, 'python', ...args] }; diff --git a/src/client/common/process/pythonDaemon.ts b/src/client/common/process/pythonDaemon.ts index 33626c57f519..f5691f83c226 100644 --- a/src/client/common/process/pythonDaemon.ts +++ b/src/client/common/process/pythonDaemon.ts @@ -81,8 +81,8 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi return this.pythonExecutionService.getExecutablePath(); } } - public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { - return { command, args }; + public getExecutionInfo(args: string[]): PythonExecutionInfo { + return { command: this.pythonPath, args }; } public async isModuleInstalled(moduleName: string): Promise { this.throwIfRPCConnectionIsDead(); diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index bdf6220d97f3..d89f60499ac5 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -42,7 +42,7 @@ export class PythonExecutionService implements IPythonExecutionService { // See these two bugs: // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 - const { command, args } = this.getExecutionInfo(this.pythonPath, [file]); + const { command, args } = this.getExecutionInfo([file]); const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000).then(output => output ? output.stdout.trim() : '--timed out--' ); // --timed out-- should cause an exception @@ -73,19 +73,19 @@ export class PythonExecutionService implements IPythonExecutionService { return this.pythonPath; } - const { command, args } = this.getExecutionInfo(this.pythonPath, ['-c', 'import sys;print(sys.executable)']); + const { command, args } = this.getExecutionInfo(['-c', 'import sys;print(sys.executable)']); return this.procService.exec(command, args, { throwOnStdErr: true }).then(output => output.stdout.trim()); } public async isModuleInstalled(moduleName: string): Promise { - const { command, args } = this.getExecutionInfo(this.pythonPath, ['-c', `import ${moduleName}`]); + const { command, args } = this.getExecutionInfo(['-c', `import ${moduleName}`]); return this.procService .exec(command, args, { throwOnStdErr: true }) .then(() => true) .catch(() => false); } - public getExecutionInfo(command: string, args: string[]): PythonExecutionInfo { - return { command, args }; + public getExecutionInfo(args: string[]): PythonExecutionInfo { + return { command: this.pythonPath, args }; } public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { @@ -102,12 +102,12 @@ export class PythonExecutionService implements IPythonExecutionService { } public async exec(args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutionInfo(this.pythonPath, args); + const executable = this.getExecutionInfo(args); return this.procService.exec(executable.command, executable.args, opts); } public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - const executable = this.getExecutionInfo(this.pythonPath, ['-m', moduleName, ...args]); + const executable = this.getExecutionInfo(['-m', moduleName, ...args]); const result = await this.procService.exec(executable.command, executable.args, opts); // If a module is not installed we'll have something in stderr. diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index a186604e06d2..f4c9beb5f5e0 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -113,7 +113,7 @@ export interface IPythonExecutionService { getInterpreterInformation(): Promise; getExecutablePath(): Promise; isModuleInstalled(moduleName: string): Promise; - getExecutionInfo(command: string, args: string[]): PythonExecutionInfo; + getExecutionInfo(args: string[]): PythonExecutionInfo; execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult; execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult; diff --git a/src/test/common/process/condaExecutionService.unit.test.ts b/src/test/common/process/condaExecutionService.unit.test.ts index 25ff3c595222..8ef07c408684 100644 --- a/src/test/common/process/condaExecutionService.unit.test.ts +++ b/src/test/common/process/condaExecutionService.unit.test.ts @@ -24,20 +24,20 @@ suite('CondaExecutionService', () => { serviceContainer.setup(s => s.get(IFileSystem)).returns(() => fileSystem.object); }); - test('getExecutionInfo with a named environment should return an executable command using the environment name', () => { + test('getExecutionInfo with a named environment should return execution info using the environment name', () => { const environment = { name: 'foo', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); - const result = executionService.getExecutionInfo(pythonPath, args); + const result = executionService.getExecutionInfo(args); expect(result).to.deep.equal({ command: condaFile, args: ['run', '-n', environment.name, 'python', ...args] }); }); - test('getExecutionInfo with a non-named environment should return an executable command using the environment path', async () => { + test('getExecutionInfo with a non-named environment should return execution info using the environment path', async () => { const environment = { name: '', path: 'bar' }; executionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, environment); - const result = executionService.getExecutionInfo(pythonPath, args); + const result = executionService.getExecutionInfo(args); expect(result).to.deep.equal({ command: condaFile, args: ['run', '-p', environment.path, 'python', ...args] }); }); diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index 46d139693e45..6e5fb6189217 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -250,12 +250,11 @@ suite('PythonExecutionService', () => { expect(result).to.eventually.be.rejectedWith(`Module '${moduleName}' not installed`); }); - test('getExecutableInfo should return the command and execution arguments as is', () => { + test('getExecutionInfo should return pythonPath and the execution arguments as is', () => { const args = ['-a', 'b', '-c']; - const command = 'command'; - const result = executionService.getExecutionInfo(command, args); + const result = executionService.getExecutionInfo(args); - expect(result).to.deep.equal({ command, args }, 'getExecutableInfo should return the command and execution arguments as is'); + expect(result).to.deep.equal({ command: pythonPath, args }, 'getExecutionInfo should return pythonPath and the command and execution arguments as is'); }); }); diff --git a/src/test/datascience/executionServiceMock.ts b/src/test/datascience/executionServiceMock.ts index baadcc685d71..1fa043a04068 100644 --- a/src/test/datascience/executionServiceMock.ts +++ b/src/test/datascience/executionServiceMock.ts @@ -67,7 +67,7 @@ export class MockPythonExecutionService implements IPythonExecutionService { return result; } - public getExecutionInfo(command: string, args: string[]) { - return { command, args }; + public getExecutionInfo(args: string[]) { + return { command: this.pythonPath, args }; } } diff --git a/src/test/datascience/mockPythonService.ts b/src/test/datascience/mockPythonService.ts index c69a8e68e858..1f8dd7212049 100644 --- a/src/test/datascience/mockPythonService.ts +++ b/src/test/datascience/mockPythonService.ts @@ -65,7 +65,7 @@ export class MockPythonService implements IPythonExecutionService { this.procService.setDelay(timeout); } - public getExecutionInfo(command: string, args: string[]) { - return { command, args }; + public getExecutionInfo(args: string[]) { + return { command: this.interpreter.path, args }; } } From ee42991e93f079190232d8397b0f73f2ca256cbc Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 11:49:07 -0800 Subject: [PATCH 47/58] Fix PythonDaemon getExecutionInfo --- src/client/common/process/pythonDaemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/common/process/pythonDaemon.ts b/src/client/common/process/pythonDaemon.ts index f5691f83c226..9d9dce7bf5a8 100644 --- a/src/client/common/process/pythonDaemon.ts +++ b/src/client/common/process/pythonDaemon.ts @@ -82,7 +82,7 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi } } public getExecutionInfo(args: string[]): PythonExecutionInfo { - return { command: this.pythonPath, args }; + return this.pythonExecutionService.getExecutionInfo(args); } public async isModuleInstalled(moduleName: string): Promise { this.throwIfRPCConnectionIsDead(); From 3442437a4f32250707af298c9f7f5336f5ddd545 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 11:50:35 -0800 Subject: [PATCH 48/58] Undo formatting changes --- src/client/common/process/pythonExecutionFactory.ts | 1 - src/client/common/process/pythonProcess.ts | 11 ++++------- .../process/pythonExecutionFactory.unit.test.ts | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index a64f8156d5de..2da2a80fa88f 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -134,7 +134,6 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); this.serviceContainer.get(IDisposableRegistry).push(processService); - return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index d89f60499ac5..aa628daa8260 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -43,9 +43,8 @@ export class PythonExecutionService implements IPythonExecutionService { // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 const { command, args } = this.getExecutionInfo([file]); - const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000).then(output => - output ? output.stdout.trim() : '--timed out--' - ); // --timed out-- should cause an exception + const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000) + .then(output => output ? output.stdout.trim() : '--timed out--'); // --timed out-- should cause an exception let json: { versionInfo: PythonVersionInfo; sysPrefix: string; sysVersion: string; is64Bit: boolean }; try { @@ -78,10 +77,8 @@ export class PythonExecutionService implements IPythonExecutionService { } public async isModuleInstalled(moduleName: string): Promise { const { command, args } = this.getExecutionInfo(['-c', `import ${moduleName}`]); - return this.procService - .exec(command, args, { throwOnStdErr: true }) - .then(() => true) - .catch(() => false); + return this.procService.exec(command, args, { throwOnStdErr: true }) + .then(() => true).catch(() => false); } public getExecutionInfo(args: string[]): PythonExecutionInfo { diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 06eb3e802617..fdc6b3721be6 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -136,7 +136,7 @@ suite('Process - PythonExecutionFactory', () => { const mockExecService = 'something'; factory.create = async (_options: ExecutionFactoryCreationOptions) => { createInvoked = true; - return Promise.resolve((mockExecService as any) as IPythonExecutionService); + return Promise.resolve(mockExecService as any as IPythonExecutionService); }; const service = await verifyCreateActivated(factory, activationHelper, resource, interpreter); From 6d176bc95b4e33b615f46cb7b376944b3dae185b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 12 Nov 2019 12:52:10 -0800 Subject: [PATCH 49/58] Fix merge gone sideways --- src/test/common/process/pythonExecutionFactory.unit.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 9c51ac821b95..4148431a1022 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { expect } from 'chai'; import { SemVer } from 'semver'; import * as sinon from 'sinon'; -import { anything, instance, mock, verify, when, anyString } from 'ts-mockito'; +import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; import { PythonSettings } from '../../../client/common/configSettings'; @@ -346,6 +346,7 @@ suite('Process - PythonExecutionFactory', () => { verify(condaService.getCondaVersion()).once(); verify(condaService.getCondaEnvironment(pythonPath)).never(); verify(condaService.getCondaFile()).never(); + }); test('Create Daemon Service an invoke initialize', async () => { const pythonSettings = mock(PythonSettings); when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); From 5dbd096ca277ad48699cfb2132e2176238c9252a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 13 Nov 2019 22:13:15 -0800 Subject: [PATCH 50/58] Fix linting functional tests --- .../common/process/pythonExecutionFactory.ts | 15 ++++++--------- src/test/linters/lint.functional.test.ts | 2 ++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index e0f8129bbf06..9e67fa6e00cd 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { lt } from 'semver'; +import { gte } from 'semver'; import { Uri } from 'vscode'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; @@ -116,18 +116,15 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { - const condaVersion = await this.condaService.getCondaVersion(); - if (!condaVersion || lt(condaVersion, CONDA_RUN_VERSION)) { - return; - } - const processServicePromise = processService ? Promise.resolve(processService) : this.processServiceFactory.create(resource); - const [condaEnvironment, condaFile, procService] = await Promise.all([ + const [condaVersion, condaEnvironment, condaFile, procService] = await Promise.all([ + this.condaService.getCondaVersion(), this.condaService.getCondaEnvironment(pythonPath), this.condaService.getCondaFile(), processServicePromise ]); - if (condaEnvironment && condaFile && procService) { + + if (condaVersion && gte(condaVersion, CONDA_RUN_VERSION) && condaEnvironment && condaFile && procService) { // Add logging to the newly created process service if (!processService) { const processLogger = this.serviceContainer.get(IProcessLogger); @@ -137,6 +134,6 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { return new CondaExecutionService(this.serviceContainer, procService, pythonPath, condaFile, condaEnvironment); } - return; + return Promise.resolve(undefined); } } diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index 76497bdddf0b..992ebe156f3e 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -249,6 +249,8 @@ class TestFixture extends BaseTestFixture { const condaService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(undefined)); + condaService.setup(c => c.getCondaVersion()).returns(() => Promise.resolve(undefined)); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); const processLogger = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); processLogger From 9c5724e92f23c12ed76bd72987cbd21655f08e87 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 13 Nov 2019 23:28:57 -0800 Subject: [PATCH 51/58] Fix unit tests --- .../common/process/pythonExecutionFactory.unit.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index 4148431a1022..77c5dbd59b02 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -236,8 +236,8 @@ suite('Process - PythonExecutionFactory', () => { verify(processFactory.create(resource)).once(); verify(pythonSettings.pythonPath).once(); verify(condaService.getCondaVersion()).once(); - verify(condaService.getCondaEnvironment(pythonPath)).never(); - verify(condaService.getCondaFile()).never(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).once(); expect(service).instanceOf(PythonExecutionService); }); @@ -282,7 +282,7 @@ suite('Process - PythonExecutionFactory', () => { const service = await factory.createActivatedEnvironment({ resource, interpreter }); - verify(condaService.getCondaFile()).never(); + verify(condaService.getCondaFile()).once(); verify(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).once(); verify(condaService.getCondaVersion()).once(); if (!interpreter) { @@ -344,8 +344,8 @@ suite('Process - PythonExecutionFactory', () => { expect(result).to.be.equal(undefined, 'createCondaExecutionService should return undefined if not in a conda environment'); verify(condaService.getCondaVersion()).once(); - verify(condaService.getCondaEnvironment(pythonPath)).never(); - verify(condaService.getCondaFile()).never(); + verify(condaService.getCondaEnvironment(pythonPath)).once(); + verify(condaService.getCondaFile()).once(); }); test('Create Daemon Service an invoke initialize', async () => { const pythonSettings = mock(PythonSettings); From 75076e416d3b20e7c09173c419aee3c0b5a84ee6 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 15 Nov 2019 11:46:39 -0800 Subject: [PATCH 52/58] Revert "Move terminal changes to another PR" This reverts commit 689bf1f9326b051202d5a29293401480baea956b. --- .../codeExecution/djangoShellCodeExecution.ts | 30 +++-- src/client/terminals/codeExecution/repl.ts | 5 +- .../codeExecution/terminalCodeExecution.ts | 45 +++++-- .../djangoShellCodeExect.unit.test.ts | 80 +++++++++--- .../terminalCodeExec.unit.test.ts | 121 +++++++++++++++--- 5 files changed, 222 insertions(+), 59 deletions(-) diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 3066ec27fb71..e1b7d23d7bab 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -9,6 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; +import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { DjangoContextInitializer } from './djangoContext'; @@ -16,31 +17,38 @@ import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvider { - constructor(@inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, + constructor( + @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IDisposableRegistry) disposableRegistry: Disposable[]) { - - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, + @inject(IDisposableRegistry) disposableRegistry: Disposable[] + ) { + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { - const pythonSettings = this.configurationService.getSettings(resource); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal.launchArgs.slice(); + + public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { + const { command, args: executableArgs } = await super.getExecutableInfo(resource, args); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace; const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py'); - args.push(managePyPath.fileToCommandArgument()); - args.push('shell'); - return { command, args }; + executableArgs.push(managePyPath.fileToCommandArgument()); + executableArgs.push('shell'); + return { command, args: executableArgs }; + } + + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { + // We need the executable info but not the 'manage.py shell' args + const { command, args } = await super.getExecutableInfo(resource); + return { command, args: args.concat(executeArgs) }; } } diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index c40cc3792995..816b3ea3df6b 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -7,6 +7,7 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -17,11 +18,11 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, + @inject(IPythonExecutionFactory) pythonExecFactory: IPythonExecutionFactory, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) platformService: IPlatformService ) { - - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService, pythonExecFactory); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index c80089d196f4..8517264c3944 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -9,6 +9,7 @@ import { Disposable, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; +import { IPythonExecutionFactory, PythonExecutionInfo } from '../../common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICodeExecutionService } from '../../terminals/types'; @@ -18,22 +19,20 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { protected terminalTitle!: string; private _terminalService!: ITerminalService; private replActive?: Promise; - constructor(@inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, + constructor( + @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IPlatformService) protected readonly platformService: IPlatformService) { + @inject(IPlatformService) protected readonly platformService: IPlatformService, + @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory + ) {} - } public async executeFile(file: Uri) { - const pythonSettings = this.configurationService.getSettings(file); - await this.setCwdForFileExecution(file); + const { command, args } = await this.getExecuteFileArgs(file, [file.fsPath.fileToCommandArgument()]); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const launchArgs = pythonSettings.terminal.launchArgs; - - await this.getTerminalService(file).sendCommand(command, launchArgs.concat(file.fsPath.fileToCommandArgument())); + await this.getTerminalService(file).sendCommand(command, args); } public async execute(code: string, resource?: Uri): Promise { @@ -50,7 +49,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { return; } this.replActive = new Promise(async resolve => { - const replCommandArgs = this.getReplCommandArgs(resource); + const replCommandArgs = await this.getExecutableInfo(resource); await this.getTerminalService(resource).sendCommand(replCommandArgs.command, replCommandArgs.args); // Give python repl time to start before we start sending text. @@ -59,11 +58,29 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.replActive; } - public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { + + public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { const pythonSettings = this.configurationService.getSettings(resource); - const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal.launchArgs.slice(); - return { command, args }; + const command = pythonSettings.pythonPath; + const launchArgs = pythonSettings.terminal.launchArgs; + + const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); + + if (condaExecutionService) { + return condaExecutionService.getExecutionInfo(command, [...launchArgs, ...args]); + } + + const isWindows = this.platformService.isWindows; + + return { + command: isWindows ? command.replace(/\\/g, '/') : command, + args: [...launchArgs, ...args] + }; + } + + // Overridden in subclasses, see djangoShellCodeExecution.ts + public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { + return this.getExecutableInfo(resource, executeArgs); } private getTerminalService(resource?: Uri): ITerminalService { if (!this._terminalService) { diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index e85da9496e88..0efc770b5afb 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -9,8 +9,11 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; +import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; @@ -23,6 +26,7 @@ suite('Terminal - Django Shell Code Execution', () => { let workspace: TypeMoq.IMock; let platform: TypeMoq.IMock; let settings: TypeMoq.IMock; + let pythonExecutionFactory: TypeMoq.IMock; let disposables: Disposable[] = []; setup(() => { const terminalFactory = TypeMoq.Mock.ofType(); @@ -30,7 +34,9 @@ suite('Terminal - Django Shell Code Execution', () => { terminalService = TypeMoq.Mock.ofType(); const configService = TypeMoq.Mock.ofType(); workspace = TypeMoq.Mock.ofType(); - workspace.setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + workspace + .setup(c => c.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => { return { dispose: () => void 0 }; @@ -39,8 +45,18 @@ suite('Terminal - Django Shell Code Execution', () => { const documentManager = TypeMoq.Mock.ofType(); const commandManager = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, - workspace.object, documentManager.object, platform.object, commandManager.object, fileSystem.object, disposables); + pythonExecutionFactory = TypeMoq.Mock.ofType(); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + platform.object, + commandManager.object, + fileSystem.object, + pythonExecutionFactory.object, + disposables + ); terminalFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny())).returns(() => terminalService.object); @@ -58,13 +74,12 @@ suite('Terminal - Django Shell Code Execution', () => { disposables = []; }); - function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, - terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { + async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[], expectedTerminalArgs: string[], resource?: Uri) { platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); - const replCommandArgs = (executor as DjangoShellCodeExecutionProvider).getReplCommandArgs(resource); + const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); @@ -75,7 +90,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { @@ -83,7 +98,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, when building repl args on Windows', async () => { @@ -91,7 +106,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure fully qualified python path is returned as is, on non Windows', async () => { @@ -99,7 +114,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure python path is returned as is, on non Windows', async () => { @@ -107,7 +122,7 @@ suite('Terminal - Django Shell Code Execution', () => { const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs); }); test('Ensure current workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -118,7 +133,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure current workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -129,7 +144,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (containing spaces) is used to prefix manage.py', async () => { @@ -141,7 +156,7 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(`${path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument()}`, 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); test('Ensure default workspace folder (without spaces) is used to prefix manage.py', async () => { @@ -153,7 +168,42 @@ suite('Terminal - Django Shell Code Execution', () => { workspace.setup(w => w.workspaceFolders).returns(() => [workspaceFolder]); const expectedTerminalArgs = terminalArgs.concat(path.join(workspaceUri.fsPath, 'manage.py').fileToCommandArgument(), 'shell'); - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs, expectedTerminalArgs, Uri.file('x')); }); + async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }, resource?: Uri) { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); + const hasEnvName = condaEnv.name !== ''; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + + const replCommandArgs = await (executor as DjangoShellCodeExecutionProvider).getExecutableInfo(resource); + + expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); + expect(replCommandArgs.command).to.be.equal(condaFile, 'Incorrect conda path'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + } + + test('Ensure conda args including env name are passed when using a conda environment with a name', async () => { + const pythonPath = 'c:/program files/python/python.exe'; + const condaPath = { name: 'foo-env', path: 'path/to/foo-env' }; + const terminalArgs = ['-a', 'b', '-c']; + + await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); + }); + + test('Ensure conda args including env path are passed when using a conda environment with an empty name', async () => { + const pythonPath = 'c:/program files/python/python.exe'; + const condaPath = { name: '', path: 'path/to/foo-env' }; + const terminalArgs = ['-a', 'b', '-c']; + + await testReplCondaCommandArguments(pythonPath, terminalArgs, condaPath); + }); }); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts index 35bffee23ddb..825b3b9b02cb 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -9,9 +9,12 @@ import * as TypeMoq from 'typemoq'; import { Disposable, Uri, WorkspaceFolder } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { CondaExecutionService } from '../../../client/common/process/condaExecutionService'; +import { IProcessService, IPythonExecutionFactory } from '../../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; +import { IServiceContainer } from '../../../client/ioc/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; @@ -33,6 +36,7 @@ suite('Terminal - Code Execution', () => { let documentManager: TypeMoq.IMock; let commandManager: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; + let pythonExecutionFactory: TypeMoq.IMock; let isDjangoRepl: boolean; teardown(() => { @@ -56,18 +60,25 @@ suite('Terminal - Code Execution', () => { documentManager = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - + pythonExecutionFactory = TypeMoq.Mock.ofType(); settings = TypeMoq.Mock.ofType(); settings.setup(s => s.terminal).returns(() => terminalSettings.object); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); switch (testSuiteName) { case 'Terminal Execution': { - executor = new TerminalCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); + executor = new TerminalCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + disposables, + platform.object, + pythonExecutionFactory.object + ); break; } case 'Repl Execution': { - executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, disposables, platform.object); + executor = new ReplProvider(terminalFactory.object, configService.object, workspace.object, pythonExecutionFactory.object, disposables, platform.object); expectedTerminalTitle = 'REPL'; break; } @@ -76,8 +87,17 @@ suite('Terminal - Code Execution', () => { workspace.setup(w => w.onDidChangeWorkspaceFolders(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return { dispose: noop }; }); - executor = new DjangoShellCodeExecutionProvider(terminalFactory.object, configService.object, workspace.object, documentManager.object, - platform.object, commandManager.object, fileSystem.object, disposables); + executor = new DjangoShellCodeExecutionProvider( + terminalFactory.object, + configService.object, + workspace.object, + documentManager.object, + platform.object, + commandManager.object, + fileSystem.object, + pythonExecutionFactory.object, + disposables + ); expectedTerminalTitle = 'Django Shell'; break; } @@ -209,11 +229,13 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); 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())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -236,51 +258,116 @@ suite('Terminal - Code Execution', () => { await testFileExecution(false, PYTHON_PATH, ['-a', '-b', '-c'], file); }); - function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { + async function testCondaFileExecution(pythonPath: string, terminalArgs: string[], file: Uri, condaEnv: { name: string; path: string }): Promise { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + terminalSettings.setup(t => t.executeInFileDir).returns(() => false); + workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); + + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + + await executor.executeFile(file); + + const hasEnvName = condaEnv.name !== ''; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; + + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(condaFile), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + } + + test('Ensure conda args with conda env name are sent to terminal if there is a conda environment with a name', async () => { + const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); + await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: 'foo-env', path: 'path/to/foo-env' }); + }); + + test('Ensure conda args with conda env path are sent to terminal if there is a conda environment without a name', async () => { + const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); + await testCondaFileExecution(PYTHON_PATH, ['-a', '-b', '-c'], file, { name: '', path: 'path/to/foo-env' }); + }); + + async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { + pythonExecutionFactory.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); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; - const replCommandArgs = (executor as TerminalCodeExecutionProvider).getReplCommandArgs(); + const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); } - test('Ensure fully qualified python path is escaped when building repl args on Windows', () => { + test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { const pythonPath = 'c:\\program files\\python\\python.exe'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); + await testReplCommandArguments(true, pythonPath, 'c:/program files/python/python.exe', terminalArgs); }); - test('Ensure fully qualified python path is returned as is, when building repl args on Windows', () => { + test('Ensure fully qualified python path is returned as is, when building repl args on Windows', async () => { const pythonPath = 'c:/program files/python/python.exe'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, when building repl args on Windows', () => { + test('Ensure python path is returned as is, when building repl args on Windows', async () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); }); - test('Ensure fully qualified python path is returned as is, on non Windows', () => { + test('Ensure fully qualified python path is returned as is, on non Windows', async () => { const pythonPath = 'usr/bin/python'; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); }); - test('Ensure python path is returned as is, on non Windows', () => { + test('Ensure python path is returned as is, on non Windows', async () => { const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; - testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + await testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); + }); + + async function testReplCondaCommandArguments(pythonPath: string, terminalArgs: string[], condaEnv: { name: string; path: string }) { + settings.setup(s => s.pythonPath).returns(() => pythonPath); + terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); + + const condaFile = 'conda'; + const serviceContainer = TypeMoq.Mock.ofType(); + const processService = TypeMoq.Mock.ofType(); + const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + + const hasEnvName = condaEnv.name !== ''; + const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; + const djangoArgs = isDjangoRepl ? ['manage.py', 'shell'] : []; + const expectedTerminalArgs = [...condaArgs, ...terminalArgs, ...djangoArgs]; + + const replCommandArgs = await (executor as TerminalCodeExecutionProvider).getExecutableInfo(); + + expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); + expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); + expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + } + + test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { + await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: 'foo-env', path: 'path/to/foo-env' }); + }); + + test('Ensure conda args with env path are returned when building repl args with a conda env without a name', async () => { + await testReplCondaCommandArguments(PYTHON_PATH, ['-a', 'b', 'c'], { name: '', path: 'path/to/foo-env' }); }); test('Ensure nothing happens when blank text is sent to the terminal', async () => { From a4f0da56813edd087c0bd7922f9ef545c1a99740 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 15 Nov 2019 11:52:45 -0800 Subject: [PATCH 53/58] Fixes --- src/client/terminals/codeExecution/terminalCodeExecution.ts | 4 ++-- src/test/common/process/pythonExecutionFactory.unit.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 8517264c3944..5e5626458f02 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -67,8 +67,8 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); if (condaExecutionService) { - return condaExecutionService.getExecutionInfo(command, [...launchArgs, ...args]); - } + return condaExecutionService.getExecutionInfo([...launchArgs, ...args]); + } const isWindows = this.platformService.isWindows; diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index a2600a098df4..e9bf043ce2b5 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -36,7 +36,6 @@ import { CondaService } from '../../../client/interpreter/locators/services/cond import { WindowsStoreInterpreter } from '../../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IWindowsStoreInterpreter } from '../../../client/interpreter/locators/types'; import { ServiceContainer } from '../../../client/ioc/container'; -import { ProcessService } from '../../../client/common/process/proc'; // tslint:disable:no-any max-func-body-length @@ -93,6 +92,7 @@ suite('Process - PythonExecutionFactory', () => { when(processLogger.logProcess('', [], {})).thenReturn(); processService = typemoq.Mock.ofType(); processService.setup(p => p.on('exec', () => { return; })).returns(() => processService.object); + processService.setup((p: any) => p.then).returns(() => undefined); const interpreterService = mock(InterpreterService); when(interpreterService.getInterpreterDetails(anything())).thenResolve({} as any); const serviceContainer = mock(ServiceContainer); From b6b3fd391f80aa75c9a14ae92d37c06823fd19fa Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 15 Nov 2019 22:39:59 -0800 Subject: [PATCH 54/58] Fix tests --- .../common/process/pythonExecutionFactory.ts | 12 +++++++++--- src/client/interpreter/locators/index.ts | 2 +- .../locators/services/cacheableLocatorService.ts | 2 +- .../codeExecution/terminalCodeExecution.ts | 3 +-- .../process/pythonExecutionFactory.unit.test.ts | 5 ++++- src/test/linters/lint.functional.test.ts | 6 +++++- .../djangoShellCodeExect.unit.test.ts | 2 +- .../codeExecution/terminalCodeExec.unit.test.ts | 16 ++++++++-------- 8 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 5acb2d1b1fdd..97de61f3ed2a 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -52,9 +52,15 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); - const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); - if (condaExecutionService) { - return condaExecutionService; + // Don't bother getting a conda execution service instance if we haven't fetched the list of interpreters yet. + // Also, without this hasInterpreters check smoke tests will time out + const interpreterService = this.serviceContainer.get(IInterpreterService); + const hasInterpreters = await interpreterService.hasInterpreters; + if (hasInterpreters) { + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; + } } if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) { diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index da725e1f4825..9bb0753cb343 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -51,7 +51,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi return new EventEmitter>().event; } public get hasInterpreters(): Promise { - return this._hasInterpreters.promise; + return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); } /** diff --git a/src/client/interpreter/locators/services/cacheableLocatorService.ts b/src/client/interpreter/locators/services/cacheableLocatorService.ts index 6d6e76580c03..8fcdeb0c39da 100644 --- a/src/client/interpreter/locators/services/cacheableLocatorService.ts +++ b/src/client/interpreter/locators/services/cacheableLocatorService.ts @@ -34,7 +34,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ return this.locating.event; } public get hasInterpreters(): Promise { - return this._hasInterpreters.promise; + return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); } public abstract dispose(): void; @traceDecorators.verbose('Get Interpreters in CacheableLocatorService') diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 5e5626458f02..d60dacc17565 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -64,8 +64,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { const command = pythonSettings.pythonPath; const launchArgs = pythonSettings.terminal.launchArgs; - const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command); - + const condaExecutionService = await this.pythonExecFactory.createCondaExecutionService(command, undefined, resource); if (condaExecutionService) { return condaExecutionService.getExecutionInfo([...launchArgs, ...args]); } diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index e9bf043ce2b5..fd4e118c338e 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -81,6 +81,7 @@ suite('Process - PythonExecutionFactory', () => { let processLogger: IProcessLogger; let processService: typemoq.IMock; let windowsStoreInterpreter: IWindowsStoreInterpreter; + let interpreterService: IInterpreterService; setup(() => { bufferDecoder = mock(BufferDecoder); activationHelper = mock(EnvironmentActivationService); @@ -93,7 +94,7 @@ suite('Process - PythonExecutionFactory', () => { processService = typemoq.Mock.ofType(); processService.setup(p => p.on('exec', () => { return; })).returns(() => processService.object); processService.setup((p: any) => p.then).returns(() => undefined); - const interpreterService = mock(InterpreterService); + interpreterService = mock(InterpreterService); when(interpreterService.getInterpreterDetails(anything())).thenResolve({} as any); const serviceContainer = mock(ServiceContainer); when(serviceContainer.get(IDisposableRegistry)).thenReturn([]); @@ -199,6 +200,7 @@ suite('Process - PythonExecutionFactory', () => { const pythonPath = 'path/to/python'; const pythonSettings = mock(PythonSettings); + when(interpreterService.hasInterpreters).thenResolve(true); when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); @@ -223,6 +225,7 @@ suite('Process - PythonExecutionFactory', () => { when(pythonSettings.pythonPath).thenReturn(pythonPath); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); when(condaService.getCondaVersion()).thenResolve(new SemVer('1.0.0')); + when(interpreterService.hasInterpreters).thenResolve(true); const service = await factory.create({ resource }); diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index 992ebe156f3e..4bcba475a609 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -36,7 +36,7 @@ import { import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; -import { ICondaService } from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterService } from '../../client/interpreter/contracts'; import { WindowsStoreInterpreter } from '../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IServiceContainer } from '../../client/ioc/types'; import { LINTERID_BY_PRODUCT } from '../../client/linters/constants'; @@ -247,6 +247,10 @@ class TestFixture extends BaseTestFixture { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IBufferDecoder), TypeMoq.It.isAny())) .returns(() => decoder); + const interpreterService = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + interpreterService.setup(i => i.hasInterpreters).returns(() => Promise.resolve(true)); + serviceContainer.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.getCondaEnvironment(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(undefined)); condaService.setup(c => c.getCondaVersion()).returns(() => Promise.resolve(undefined)); diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts index 0efc770b5afb..c0caba42a3d7 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts @@ -182,7 +182,7 @@ suite('Terminal - Django Shell Code Execution', () => { const hasEnvName = condaEnv.name !== ''; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const expectedTerminalArgs = [...condaArgs, ...terminalArgs, 'manage.py', 'shell']; - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), 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 825b3b9b02cb..771390fe712b 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts @@ -229,13 +229,13 @@ suite('Terminal - Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); 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())).returns(() => Promise.resolve(undefined)); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, file), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { @@ -268,7 +268,7 @@ suite('Terminal - Code Execution', () => { const serviceContainer = TypeMoq.Mock.ofType(); const processService = TypeMoq.Mock.ofType(); const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); await executor.executeFile(file); @@ -276,7 +276,7 @@ suite('Terminal - Code Execution', () => { const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; const expectedArgs = [...condaArgs, ...terminalArgs, file.fsPath.fileToCommandArgument()]; - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, file), TypeMoq.Times.once()); terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(condaFile), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); } @@ -291,7 +291,7 @@ suite('Terminal - Code Execution', () => { }); async function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); platform.setup(p => p.isWindows).returns(() => isWindows); settings.setup(s => s.pythonPath).returns(() => pythonPath); terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); @@ -301,7 +301,7 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Command args is undefined'); expect(replCommandArgs.command).to.be.equal(expectedPythonPath, 'Incorrect python path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, undefined), TypeMoq.Times.once()); } test('Ensure fully qualified python path is escaped when building repl args on Windows', async () => { @@ -347,7 +347,7 @@ suite('Terminal - Code Execution', () => { const serviceContainer = TypeMoq.Mock.ofType(); const processService = TypeMoq.Mock.ofType(); const condaExecutionService = new CondaExecutionService(serviceContainer.object, processService.object, pythonPath, condaFile, condaEnv); - pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); + pythonExecutionFactory.setup(p => p.createCondaExecutionService(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(condaExecutionService)); const hasEnvName = condaEnv.name !== ''; const condaArgs = ['run', ...(hasEnvName ? ['-n', condaEnv.name] : ['-p', condaEnv.path]), 'python']; @@ -359,7 +359,7 @@ suite('Terminal - Code Execution', () => { expect(replCommandArgs).not.to.be.an('undefined', 'Conda command args are undefined'); expect(replCommandArgs.command).to.be.equal('conda', 'Incorrect conda path'); expect(replCommandArgs.args).to.be.deep.equal(expectedTerminalArgs, 'Incorrect conda arguments'); - pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath), TypeMoq.Times.once()); + pythonExecutionFactory.verify(async p => p.createCondaExecutionService(pythonPath, undefined, undefined), TypeMoq.Times.once()); } test('Ensure conda args with env name are returned when building repl args with a conda env with a name', async () => { From eddcf155ede40e96c74f48ad195dfae27cb4d8e3 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 19 Nov 2019 16:42:37 -0800 Subject: [PATCH 55/58] Fix single workspace tests --- src/test/common/installer.test.ts | 1 + src/test/format/extension.format.test.ts | 3 +- src/test/refactor/rename.test.ts | 5 ++- src/test/serviceRegistry.ts | 35 ++++++++++++++++++- .../testing/nosetest/nosetest.run.test.ts | 5 ++- src/test/testing/nosetest/nosetest.test.ts | 2 +- src/test/testing/pytest/pytest.test.ts | 5 ++- 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index a391696c0c4c..c50c46fef0b2 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -75,6 +75,7 @@ suite('Installer', () => { ioc.serviceManager.addSingleton(IWorkspaceService, WorkspaceService); ioc.serviceManager.addSingleton(ICondaService, CondaService); + ioc.registerMockInterpreterTypes(); ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); ioc.serviceManager.addSingletonInstance(IProductService, new ProductService()); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index d0abe5b3eaaf..ed0986d8e188 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -105,10 +105,11 @@ suite('Formatting - General', () => { ioc.serviceManager.addSingleton(InterpreterHashProvider, InterpreterHashProvider); ioc.serviceManager.addSingleton(InterpeterHashProviderFactory, InterpeterHashProviderFactory); ioc.serviceManager.addSingleton(InterpreterFilter, InterpreterFilter); + ioc.serviceManager.addSingleton(ICondaService, CondaService); // Mocks. ioc.registerMockProcessTypes(); - ioc.serviceManager.addSingleton(ICondaService, CondaService); + ioc.registerMockInterpreterTypes(); } async function injectFormatOutput(outputFileName: string) { diff --git a/src/test/refactor/rename.test.ts b/src/test/refactor/rename.test.ts index 6f90209c0eb6..c1d633f552de 100644 --- a/src/test/refactor/rename.test.ts +++ b/src/test/refactor/rename.test.ts @@ -17,7 +17,7 @@ import { PythonExecutionFactory } from '../../client/common/process/pythonExecut import { IProcessLogger, IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../client/common/types'; import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; -import { ICondaService } from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterService } from '../../client/interpreter/contracts'; import { WindowsStoreInterpreter } from '../../client/interpreter/locators/services/windowsStoreInterpreter'; import { IServiceContainer } from '../../client/ioc/types'; import { RefactorProxy } from '../../client/refactor/proxy'; @@ -43,11 +43,14 @@ suite('Refactor Rename', () => { const condaService = typeMoq.Mock.ofType(); const processServiceFactory = typeMoq.Mock.ofType(); processServiceFactory.setup(p => p.create(typeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder()))); + const interpreterService = typeMoq.Mock.ofType(); + interpreterService.setup(i => i.hasInterpreters).returns (() => Promise.resolve(true)); const envActivationService = typeMoq.Mock.ofType(); envActivationService.setup(e => e.getActivatedEnvironmentVariables(typeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); serviceContainer = typeMoq.Mock.ofType(); serviceContainer.setup(s => s.get(typeMoq.It.isValue(IConfigurationService), typeMoq.It.isAny())).returns(() => configService.object); serviceContainer.setup(s => s.get(typeMoq.It.isValue(IProcessServiceFactory), typeMoq.It.isAny())).returns(() => processServiceFactory.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IInterpreterService), typeMoq.It.isAny())).returns(() => interpreterService.object); serviceContainer.setup(s => s.get(typeMoq.It.isValue(IEnvironmentActivationService), typeMoq.It.isAny())) .returns(() => envActivationService.object); const windowsStoreInterpreter = mock(WindowsStoreInterpreter); diff --git a/src/test/serviceRegistry.ts b/src/test/serviceRegistry.ts index 6c390f9cdb10..11335a1ba111 100644 --- a/src/test/serviceRegistry.ts +++ b/src/test/serviceRegistry.ts @@ -11,8 +11,9 @@ import { IS_WINDOWS } from '../client/common/platform/constants'; import { FileSystem } from '../client/common/platform/fileSystem'; import { PathUtils } from '../client/common/platform/pathUtils'; import { PlatformService } from '../client/common/platform/platformService'; +import { RegistryImplementation } from '../client/common/platform/registry'; import { registerTypes as platformRegisterTypes } from '../client/common/platform/serviceRegistry'; -import { IFileSystem, IPlatformService } from '../client/common/platform/types'; +import { IFileSystem, IPlatformService, IRegistry } from '../client/common/platform/types'; import { BufferDecoder } from '../client/common/process/decoder'; import { ProcessService } from '../client/common/process/proc'; import { PythonExecutionFactory } from '../client/common/process/pythonExecutionFactory'; @@ -26,10 +27,24 @@ import { registerTypes as formattersRegisterTypes } from '../client/formatters/s import { EnvironmentActivationService } from '../client/interpreter/activation/service'; import { IEnvironmentActivationService } from '../client/interpreter/activation/types'; import { IInterpreterAutoSelectionService, IInterpreterAutoSeletionProxyService } from '../client/interpreter/autoSelection/types'; +import { CONDA_ENV_FILE_SERVICE, CONDA_ENV_SERVICE, CURRENT_PATH_SERVICE, GLOBAL_VIRTUAL_ENV_SERVICE, IInterpreterLocatorHelper, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, IPipEnvService, KNOWN_PATH_SERVICE, PIPENV_SERVICE, WINDOWS_REGISTRY_SERVICE, WORKSPACE_VIRTUAL_ENV_SERVICE } from '../client/interpreter/contracts'; +import { InterpreterService } from '../client/interpreter/interpreterService'; +import { PythonInterpreterLocatorService } from '../client/interpreter/locators'; +import { InterpreterLocatorHelper } from '../client/interpreter/locators/helpers'; +import { CondaEnvFileService } from '../client/interpreter/locators/services/condaEnvFileService'; +import { CondaEnvService } from '../client/interpreter/locators/services/condaEnvService'; +import { CurrentPathService } from '../client/interpreter/locators/services/currentPathService'; +import { GlobalVirtualEnvService } from '../client/interpreter/locators/services/globalVirtualEnvService'; import { InterpreterHashProvider } from '../client/interpreter/locators/services/hashProvider'; import { InterpeterHashProviderFactory } from '../client/interpreter/locators/services/hashProviderFactory'; import { InterpreterFilter } from '../client/interpreter/locators/services/interpreterFilter'; +import { KnownPathsService } from '../client/interpreter/locators/services/KnownPathsService'; +import { PipEnvService } from '../client/interpreter/locators/services/pipEnvService'; +import { PipEnvServiceHelper } from '../client/interpreter/locators/services/pipEnvServiceHelper'; +import { WindowsRegistryService } from '../client/interpreter/locators/services/windowsRegistryService'; import { WindowsStoreInterpreter } from '../client/interpreter/locators/services/windowsStoreInterpreter'; +import { WorkspaceVirtualEnvService } from '../client/interpreter/locators/services/workspaceVirtualEnvService'; +import { IPipEnvServiceHelper } from '../client/interpreter/locators/types'; import { registerTypes as interpretersRegisterTypes } from '../client/interpreter/serviceRegistry'; import { ServiceContainer } from '../client/ioc/container'; import { ServiceManager } from '../client/ioc/serviceManager'; @@ -135,6 +150,24 @@ export class IocContainer { this.serviceManager.rebindInstance(IEnvironmentActivationService, instance(mockEnvironmentActivationService)); } + public registerMockInterpreterTypes() { + this.serviceManager.addSingleton(IInterpreterService, InterpreterService); + this.serviceManager.addSingleton(IInterpreterLocatorService, PythonInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, CondaEnvFileService, CONDA_ENV_FILE_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, CondaEnvService, CONDA_ENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, CurrentPathService, CURRENT_PATH_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, KnownPathsService, KNOWN_PATH_SERVICE); + this.serviceManager.addSingleton(IPipEnvService, PipEnvService); + + this.serviceManager.addSingleton(IInterpreterLocatorHelper, InterpreterLocatorHelper); + this.serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper); + this.serviceManager.addSingleton(IRegistry, RegistryImplementation); + } + public registerMockProcess() { this.serviceManager.addSingletonInstance(IsWindows, IS_WINDOWS); diff --git a/src/test/testing/nosetest/nosetest.run.test.ts b/src/test/testing/nosetest/nosetest.run.test.ts index 963e4d9a01b7..13e8512d17e8 100644 --- a/src/test/testing/nosetest/nosetest.run.test.ts +++ b/src/test/testing/nosetest/nosetest.run.test.ts @@ -7,8 +7,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; import { IProcessServiceFactory } from '../../../client/common/process/types'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { InterpreterService } from '../../../client/interpreter/interpreterService'; +import { ICondaService } from '../../../client/interpreter/contracts'; import { CondaService } from '../../../client/interpreter/locators/services/condaService'; import { InterpreterHashProvider } from '../../../client/interpreter/locators/services/hashProvider'; import { InterpeterHashProviderFactory } from '../../../client/interpreter/locators/services/hashProviderFactory'; @@ -66,8 +65,8 @@ suite('Unit Tests - nose - run against actual python process', () => { ioc.registerVariableTypes(); ioc.registerMockProcessTypes(); + ioc.registerMockInterpreterTypes(); ioc.serviceManager.addSingleton(ICondaService, CondaService); - ioc.serviceManager.addSingleton(IInterpreterService, InterpreterService); ioc.serviceManager.addSingleton(WindowsStoreInterpreter, WindowsStoreInterpreter); ioc.serviceManager.addSingleton(InterpreterHashProvider, InterpreterHashProvider); diff --git a/src/test/testing/nosetest/nosetest.test.ts b/src/test/testing/nosetest/nosetest.test.ts index b601d8d5b81f..454393aa224c 100644 --- a/src/test/testing/nosetest/nosetest.test.ts +++ b/src/test/testing/nosetest/nosetest.test.ts @@ -57,8 +57,8 @@ suite('Unit Tests - nose - discovery against actual python process', () => { ioc.registerProcessTypes(); ioc.registerUnitTestTypes(); ioc.registerVariableTypes(); + ioc.registerMockInterpreterTypes(); ioc.serviceManager.addSingleton(ICondaService, CondaService); - ioc.serviceManager.addSingleton(IInterpreterService, InterpreterService); } test('Discover Tests (single test file)', async () => { diff --git a/src/test/testing/pytest/pytest.test.ts b/src/test/testing/pytest/pytest.test.ts index e580d22b8824..c7debe5b6249 100644 --- a/src/test/testing/pytest/pytest.test.ts +++ b/src/test/testing/pytest/pytest.test.ts @@ -2,8 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { InterpreterService } from '../../../client/interpreter/interpreterService'; +import { ICondaService } from '../../../client/interpreter/contracts'; import { CondaService } from '../../../client/interpreter/locators/services/condaService'; import { CommandSource } from '../../../client/testing/common/constants'; import { ITestManagerFactory } from '../../../client/testing/common/types'; @@ -36,8 +35,8 @@ suite('Unit Tests - pytest - discovery against actual python process', () => { ioc.registerProcessTypes(); ioc.registerUnitTestTypes(); ioc.registerVariableTypes(); + ioc.registerMockInterpreterTypes(); ioc.serviceManager.addSingleton(ICondaService, CondaService); - ioc.serviceManager.addSingleton(IInterpreterService, InterpreterService); } test('Discover Tests (single test file)', async () => { From 269c54b953eee508a806266b26c6d10dab27dc38 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Tue, 19 Nov 2019 16:56:37 -0800 Subject: [PATCH 56/58] Remove unused declaration --- src/test/testing/nosetest/nosetest.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/testing/nosetest/nosetest.test.ts b/src/test/testing/nosetest/nosetest.test.ts index 454393aa224c..67725aa2e44e 100644 --- a/src/test/testing/nosetest/nosetest.test.ts +++ b/src/test/testing/nosetest/nosetest.test.ts @@ -3,8 +3,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { InterpreterService } from '../../../client/interpreter/interpreterService'; +import { ICondaService } from '../../../client/interpreter/contracts'; import { CondaService } from '../../../client/interpreter/locators/services/condaService'; import { CommandSource } from '../../../client/testing/common/constants'; import { ITestManagerFactory } from '../../../client/testing/common/types'; From 394c85f46a9a4ae8aa81e935f7c8f7c985460cfa Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 20 Nov 2019 11:09:19 -0800 Subject: [PATCH 57/58] Move createConda around in createActivatedEnv --- src/client/common/process/pythonExecutionFactory.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 97de61f3ed2a..703ac3ab31e2 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -106,22 +106,23 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { }); } public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise { - const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; - const condaExecutionService = await this.createCondaExecutionService(pythonPath, undefined, options.resource); - if (condaExecutionService) { - return condaExecutionService; - } - const envVars = await this.activationHelper.getActivatedEnvironmentVariables(options.resource, options.interpreter, options.allowEnvironmentFetchExceptions); const hasEnvVars = envVars && Object.keys(envVars).length > 0; sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, undefined, { hasEnvVars }); if (!hasEnvVars) { return this.create({ resource: options.resource, pythonPath: options.interpreter ? options.interpreter.path : undefined }); } + const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; const processService: IProcessService = new ProcessService(this.decoder, { ...envVars }); const processLogger = this.serviceContainer.get(IProcessLogger); processService.on('exec', processLogger.logProcess.bind(processLogger)); this.serviceContainer.get(IDisposableRegistry).push(processService); + + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; + } + return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise { From 0f93c43252fa67020e461ebde92a12499e508da2 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 20 Nov 2019 11:51:41 -0800 Subject: [PATCH 58/58] Fix unit tests --- src/test/common/process/pythonExecutionFactory.unit.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index fd4e118c338e..bbd712fd0103 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -243,6 +243,7 @@ suite('Process - PythonExecutionFactory', () => { when(processFactory.create(resource)).thenResolve(processService.object); when(pythonSettings.pythonPath).thenReturn(pythonPath); + when(activationHelper.getActivatedEnvironmentVariables(resource, anything(), anything())).thenResolve({ x: '1' }); when(configService.getSettings(resource)).thenReturn(instance(pythonSettings)); when(condaService.getCondaVersion()).thenResolve(new SemVer(CONDA_RUN_VERSION)); when(condaService.getCondaEnvironment(anyString())).thenResolve({ name: 'foo', path: 'path/to/foo/env' });