diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index 10acd9f3bb64..83d519be9ac0 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -61,8 +61,7 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller args.push(moduleName); return { args, - execPath: condaFile, - moduleName: '' + execPath: condaFile }; } private async isCurrentEnvironmentACondaEnvironment(resource?: Uri): Promise { diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 5fe19952bba9..5acfab18c9a1 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -10,11 +10,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { PythonSettings } from '../configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { noop } from '../core.utils'; import { ITerminalServiceFactory } from '../terminal/types'; -import { ExecutionInfo, IOutputChannel } from '../types'; +import { ExecutionInfo, IConfigurationService, IOutputChannel } from '../types'; @injectable() export abstract class ModuleInstaller { @@ -23,9 +22,12 @@ export abstract class ModuleInstaller { const executionInfo = await this.getExecutionInfo(name, resource); const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); + const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource); if (executionInfo.moduleName) { - const settings = PythonSettings.getInstance(resource); - const args = ['-m', 'pip'].concat(executionInfo.args); + const configService = this.serviceContainer.get(IConfigurationService); + const settings = configService.getSettings(resource); + const args = ['-m', executionInfo.moduleName].concat(executionInfoArgs); + const pythonPath = settings.pythonPath; const interpreterService = this.serviceContainer.get(IInterpreterService); @@ -43,12 +45,28 @@ export abstract class ModuleInstaller { await terminalService.sendCommand(pythonPath, args.concat(['--user'])); } } else { - await terminalService.sendCommand(executionInfo.execPath!, executionInfo.args); + await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs); } } public abstract isSupported(resource?: vscode.Uri): Promise; protected abstract getExecutionInfo(moduleName: string, resource?: vscode.Uri): Promise; + private async processInstallArgs(args: string[], resource?: vscode.Uri): Promise { + const indexOfPylint = args.findIndex(arg => arg.toUpperCase() === 'PYLINT'); + if (indexOfPylint === -1) { + return args; + } + // If installing pylint on python 2.x, then use pylint~=1.9.0 + const interpreterService = this.serviceContainer.get(IInterpreterService); + const currentInterpreter = await interpreterService.getActiveInterpreter(resource); + if (currentInterpreter && currentInterpreter.version_info && currentInterpreter.version_info[0] === 2) { + const newArgs = [...args]; + // This command could be sent to the terminal, hence '<' needs to be escaped for UNIX. + newArgs[indexOfPylint] = '"pylint<2.0.0"'; + return newArgs; + } + return args; + } private async isPathWritableAsync(directoryPath: string): Promise { const filePath = `${directoryPath}${path.sep}___vscpTest___`; return new Promise(resolve => { diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index 4b01df9fd3e2..2833aa218324 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -5,13 +5,14 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { ITerminalServiceFactory } from '../terminal/types'; +import { ExecutionInfo } from '../types'; +import { ModuleInstaller } from './moduleInstaller'; import { IModuleInstaller } from './types'; -const pipenvName = 'pipenv'; +export const pipenvName = 'pipenv'; @injectable() -export class PipEnvInstaller implements IModuleInstaller { +export class PipEnvInstaller extends ModuleInstaller implements IModuleInstaller { private readonly pipenv: IInterpreterLocatorService; public get displayName() { @@ -21,17 +22,18 @@ export class PipEnvInstaller implements IModuleInstaller { return 10; } - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); this.pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); } - - public installModule(name: string, resource?: Uri): Promise { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); - return terminalService.sendCommand(pipenvName, ['install', name, '--dev']); - } - public async isSupported(resource?: Uri): Promise { const interpreters = await this.pipenv.getInterpreters(resource); return interpreters && interpreters.length > 0; } + protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { + return { + args: ['install', moduleName, '--dev'], + execPath: pipenvName + }; + } } diff --git a/src/client/common/installer/pipInstaller.ts b/src/client/common/installer/pipInstaller.ts index 90a471b382b1..16f886f29e93 100644 --- a/src/client/common/installer/pipInstaller.ts +++ b/src/client/common/installer/pipInstaller.ts @@ -2,8 +2,9 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { Uri, workspace } from 'vscode'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; +import { IWorkspaceService } from '../application/types'; import { IPythonExecutionFactory } from '../process/types'; import { ExecutionInfo } from '../types'; import { ModuleInstaller } from './moduleInstaller'; @@ -25,14 +26,14 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller { } protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { const proxyArgs: string[] = []; - const proxy = workspace.getConfiguration('http').get('proxy', ''); + const workspaceService = this.serviceContainer.get(IWorkspaceService); + const proxy = workspaceService.getConfiguration('http').get('proxy', ''); if (proxy.length > 0) { proxyArgs.push('--proxy'); proxyArgs.push(proxy); } return { args: [...proxyArgs, 'install', '-U', moduleName], - execPath: '', moduleName: 'pip' }; } diff --git a/src/test/common/installer/moduleInstaller.test.ts b/src/test/common/installer/moduleInstaller.test.ts deleted file mode 100644 index a7590f952896..000000000000 --- a/src/test/common/installer/moduleInstaller.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Disposable} from 'vscode'; -import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; -import { PipInstaller } from '../../../client/common/installer/pipInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; -import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; -import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../../client/common/types'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { initialize } from '../../initialize'; - -// tslint:disable-next-line:max-func-body-length -suite('Module Installerx', () => { - const pythonPath = path.join(__dirname, 'python'); - suiteSetup(initialize); - [CondaInstaller, PipInstaller].forEach(installerClass => { - let disposables: Disposable[] = []; - let installer: IModuleInstaller; - let installationChannel: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let terminalService: TypeMoq.IMock; - let pythonSettings: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - - disposables = []; - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); - - installationChannel = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); - - const condaService = TypeMoq.Mock.ofType(); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); - - const configService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService); - pythonSettings = TypeMoq.Mock.ofType(); - pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - - terminalService = TypeMoq.Mock.ofType(); - const terminalServiceFactory = TypeMoq.Mock.ofType(); - terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); - - interpreterService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); - - installer = new installerClass(serviceContainer.object); - }); - teardown(() => { - disposables.forEach(disposable => { - if (disposable) { - disposable.dispose(); - } - }); - }); - test(`Ensure getActiveInterperter is used (${installerClass.name})`, async () => { - if (installer.displayName !== 'Pip') { - return; - } - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)).verifiable(); - try { - await installer.installModule('xyz'); - // tslint:disable-next-line:no-empty - } catch { } - interpreterService.verifyAll(); - }); - }); -}); diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts new file mode 100644 index 000000000000..c57c9befe149 --- /dev/null +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length no-invalid-this + +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Disposable, OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../../client/common/application/types'; +import { noop } from '../../../client/common/core.utils'; +import { EnumEx } from '../../../client/common/enumUtils'; +import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; +import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; +import { PipInstaller } from '../../../client/common/installer/pipInstaller'; +import { ProductInstaller } from '../../../client/common/installer/productInstaller'; +import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; +import { PythonVersionInfo } from '../../../client/common/process/types'; +import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; +import { IConfigurationService, IDisposableRegistry, IPythonSettings, ModuleNamePurpose, Product } from '../../../client/common/types'; +import { ICondaService, IInterpreterService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; + +/* Complex test to ensure we cover all combinations: +We could have written separate tests for each installer, but we'd be replicate code. +Both approachs have their benefits. + +Comnbinations of: +1. With and without a workspace. +2. Http Proxy configuration. +3. All products. +4. Different versions of Python. +5. With and without conda. +6. Conda environments with names and without names. +7. All installers. +*/ +suite('Module Installer', () => { + const pythonPath = path.join(__dirname, 'python'); + [CondaInstaller, PipInstaller, PipEnvInstaller].forEach(installerClass => { + // Proxy info is relevant only for PipInstaller. + const proxyServers = installerClass === PipInstaller ? ['', 'proxy:1234'] : ['']; + proxyServers.forEach(proxyServer => { + [undefined, Uri.file('/users/dev/xyz')].forEach(resource => { + // Conda info is relevant only for CondaInstaller. + const condaEnvs = installerClass === CondaInstaller ? [{ name: 'My-Env01', path: '' }, { name: '', path: '/conda/path' }] : []; + [undefined, ...condaEnvs].forEach(condaEnvInfo => { + const testProxySuffix = proxyServer.length === 0 ? 'without proxy info' : 'with proxy info'; + const testCondaEnv = condaEnvInfo ? (condaEnvInfo.name ? 'without conda name' : 'with conda path') : 'without conda'; + const testSuite = [testProxySuffix, testCondaEnv].filter(item => item.length > 0).join(', '); + suite(`${installerClass.name} (${testSuite})`, () => { + let disposables: Disposable[] = []; + let installer: IModuleInstaller; + let installationChannel: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let terminalService: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + const condaExecutable = 'my.exe'; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + + disposables = []; + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); + + installationChannel = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + + const condaService = TypeMoq.Mock.ofType(); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve(condaExecutable)); + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(condaEnvInfo)); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); + pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + + terminalService = TypeMoq.Mock.ofType(); + const terminalServiceFactory = TypeMoq.Mock.ofType(); + terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService), TypeMoq.It.isAny())).returns(() => condaService.object); + + const workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => proxyServer); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + + installer = new installerClass(serviceContainer.object); + }); + teardown(() => { + disposables.forEach(disposable => { + if (disposable) { + disposable.dispose(); + } + }); + }); + function setActiveInterpreter(activeInterpreter?: PythonInterpreter) { + interpreterService + .setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(resource))) + .returns(() => Promise.resolve(activeInterpreter)) + .verifiable(TypeMoq.Times.atLeastOnce()); + } + getModuleNamesForTesting().forEach(product => { + const moduleName = product.moduleName; + async function installModuleAndVerifyCommand(command: string, expectedArgs: string[]) { + terminalService.setup(t => t.sendCommand(TypeMoq.It.isValue(command), TypeMoq.It.isValue(expectedArgs))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + await installer.installModule(moduleName, resource); + terminalService.verifyAll(); + } + + if (product.value === Product.pylint) { + // tslint:disable-next-line:no-shadowed-variable + generatePythonInterpreterVersions().forEach(interpreterInfo => { + const majorVersion = interpreterInfo.version_info[0]; + if (majorVersion === 2) { + const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', '"pylint<2.0.0"']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', '"pylint<2.0.0"', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('"pylint<2.0.0"'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } else { + const testTitle = `Ensure install arg is \'pylint\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', 'pylint']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', 'pylint', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('pylint'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } + }); + return; + } + + if (installerClass === PipInstaller) { + test(`Ensure getActiveInterperter is used in PipInstaller (${product.name})`, async () => { + setActiveInterpreter(); + try { + await installer.installModule(product.name, resource); + } catch { + noop(); + } + interpreterService.verifyAll(); + }); + } + if (installerClass === PipInstaller) { + test(`Test Args (${product.name})`, async () => { + setActiveInterpreter(); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', moduleName]; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + interpreterService.verifyAll(); + }); + } + if (installerClass === PipEnvInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install', moduleName, '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push(moduleName); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + }); + }); + }); + }); + }); + }); +}); + +function generatePythonInterpreterVersions() { + const versions: PythonVersionInfo[] = [[2, 7, 0, 'final'], [3, 4, 0, 'final'], [3, 5, 0, 'final'], [3, 6, 0, 'final'], [3, 7, 0, 'final']]; + return versions.map(version => { + const info = TypeMoq.Mock.ofType(); + info.setup((t: any) => t.then).returns(() => undefined); + info.setup(t => t.type).returns(() => InterpreterType.VirtualEnv); + info.setup(t => t.version_info).returns(() => version); + return info.object; + }); +} + +function getModuleNamesForTesting(): { name: string; value: Product; moduleName: string }[] { + return EnumEx.getNamesAndValues(Product) + .map(product => { + let moduleName = ''; + const mockSvc = TypeMoq.Mock.ofType().object; + const mockOutChnl = TypeMoq.Mock.ofType().object; + try { + const prodInstaller = new ProductInstaller(mockSvc, mockOutChnl); + moduleName = prodInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.install); + return { name: product.name, value: product.value, moduleName }; + } catch { + return; + } + }) + .filter(item => item !== undefined) as { name: string; value: Product; moduleName: string }[]; +} diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 291cc6d3f534..0cf8698fc644 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -3,7 +3,8 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { ConfigurationTarget, Uri } from 'vscode'; +import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../client/common/application/types'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; import { CondaInstaller } from '../../client/common/installer/condaInstaller'; @@ -103,6 +104,12 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IPlatformService, PlatformService); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); + const workspaceService = TypeMoq.Mock.ofType(); + ioc.serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => ''); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); } diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 8a8c41bfcb69..f2c965417650 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -91,7 +91,7 @@ if (require.main === module) { const timeoutArgIndex = args.findIndex(arg => arg.startsWith('timeout=')); const grepArgIndex = args.findIndex(arg => arg.startsWith('grep=')); const timeout: number | undefined = timeoutArgIndex >= 0 ? parseInt(args[timeoutArgIndex].split('=')[1].trim(), 10) : undefined; - let grep: string | undefined = timeoutArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; + let grep: string | undefined = grepArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; grep = grep && grep.length > 0 ? grep : undefined; runTests({ grep, timeout });