diff --git a/news/2 Fixes/2827.md b/news/2 Fixes/2827.md new file mode 100644 index 000000000000..d3c96b186de4 --- /dev/null +++ b/news/2 Fixes/2827.md @@ -0,0 +1 @@ +Disable activation of conda environments in PowerShell. diff --git a/src/client/application/diagnostics/checks/powerShellActivation.ts b/src/client/application/diagnostics/checks/powerShellActivation.ts new file mode 100644 index 000000000000..60a6a63dc759 --- /dev/null +++ b/src/client/application/diagnostics/checks/powerShellActivation.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import '../../../common/extensions'; +import { error } from '../../../common/logger'; +import { useCommandPromptAsDefaultShell } from '../../../common/terminal/commandPrompt'; +import { IConfigurationService, ICurrentProcess } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { IDiagnosticsCommandFactory } from '../commands/types'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +const PowershellActivationNotSupportedWithBatchFilesMessage = 'Activation of the selected Python environment is not supported in PowerShell. Consider changing your shell to Command Prompt.'; + +export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic { + constructor() { + super(DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic, + PowershellActivationNotSupportedWithBatchFilesMessage, + DiagnosticSeverity.Warning, DiagnosticScope.Global); + } +} + +export const PowerShellActivationHackDiagnosticsServiceId = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic'; + +@injectable() +export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsService { + protected readonly messageService: IDiagnosticHandlerService; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super([DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic], serviceContainer); + this.messageService = serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); + } + public async diagnose(): Promise { + return []; + } + public async handle(diagnostics: IDiagnostic[]): Promise { + // This class can only handle one type of diagnostic, hence just use first item in list. + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + const diagnostic = diagnostics[0]; + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } + const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); + const currentProcess = this.serviceContainer.get(ICurrentProcess); + const configurationService = this.serviceContainer.get(IConfigurationService); + const options = [ + { + prompt: 'Use Command Prompt', + // tslint:disable-next-line:no-object-literal-type-assertion + command: { + diagnostic, invoke: async (): Promise => { + useCommandPromptAsDefaultShell(currentProcess, configurationService) + .catch(ex => error('Use Command Prompt as default shell', ex)); + } + } + }, + { + prompt: 'Ignore' + }, + { + prompt: 'Always Ignore', + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) + }, + { + prompt: 'More Info', + command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://aka.ms/Niq35h' }) + } + ]; + + await this.messageService.handle(diagnostic, { commandPrompts: options }); + } +} diff --git a/src/client/application/diagnostics/constants.ts b/src/client/application/diagnostics/constants.ts index be66267c298d..6ee60340b4b9 100644 --- a/src/client/application/diagnostics/constants.ts +++ b/src/client/application/diagnostics/constants.ts @@ -9,5 +9,6 @@ export enum DiagnosticCodes { NoPythonInterpretersDiagnostic = 'NoPythonInterpretersDiagnostic', MacInterpreterSelectedAndNoOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndNoOtherInterpretersDiagnostic', MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic', - InvalidPythonPathInDebuggerDiagnostic = 'InvalidPythonPathInDebuggerDiagnostic' + InvalidPythonPathInDebuggerDiagnostic = 'InvalidPythonPathInDebuggerDiagnostic', + EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic' } diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts index 6674947ec863..3534581bb73c 100644 --- a/src/client/application/diagnostics/serviceRegistry.ts +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -7,6 +7,7 @@ import { IServiceManager } from '../../ioc/types'; import { EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId } from './checks/envPathVariable'; import { InvalidDebuggerTypeDiagnosticsService, InvalidDebuggerTypeDiagnosticsServiceId } from './checks/invalidDebuggerType'; import { InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId } from './checks/invalidPythonPathInDebugger'; +import { PowerShellActivationHackDiagnosticsService, PowerShellActivationHackDiagnosticsServiceId } from './checks/powerShellActivation'; import { InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId } from './checks/pythonInterpreter'; import { DiagnosticsCommandFactory } from './commands/factory'; import { IDiagnosticsCommandFactory } from './commands/types'; @@ -21,5 +22,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IDiagnosticsService, InvalidDebuggerTypeDiagnosticsService, InvalidDebuggerTypeDiagnosticsServiceId); serviceManager.addSingleton(IDiagnosticsService, InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId); serviceManager.addSingleton(IDiagnosticsService, InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId); + serviceManager.addSingleton(IDiagnosticsService, PowerShellActivationHackDiagnosticsService, PowerShellActivationHackDiagnosticsServiceId); serviceManager.addSingleton(IDiagnosticsCommandFactory, DiagnosticsCommandFactory); } diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index fe84a3de9d5c..17c1b6989d2d 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -12,11 +12,16 @@ export class ConfigurationService implements IConfigurationService { return PythonSettings.getInstance(resource); } - public async updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise { - const settingsInfo = PythonSettings.getSettingsUriAndTarget(resource); + public async updateSectionSetting(section: string, setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise { + const settingsInfo = section === 'python' ? + PythonSettings.getSettingsUriAndTarget(resource) : + { + uri: resource, + target: configTarget ? configTarget : ConfigurationTarget.WorkspaceFolder + }; - const pythonConfig = workspace.getConfiguration('python', settingsInfo.uri); - const currentValue = pythonConfig.inspect(setting); + const configSection = workspace.getConfiguration(section, settingsInfo.uri); + const currentValue = configSection.inspect(setting); if (currentValue !== undefined && ((settingsInfo.target === ConfigurationTarget.Global && currentValue.globalValue === value) || @@ -25,19 +30,23 @@ export class ConfigurationService implements IConfigurationService { return; } - await pythonConfig.update(setting, value, settingsInfo.target); - await this.verifySetting(pythonConfig, settingsInfo.target, setting, value); + await configSection.update(setting, value, settingsInfo.target); + await this.verifySetting(configSection, settingsInfo.target, setting, value); + } + + public async updateSetting(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise { + return this.updateSectionSetting('python', setting, value, resource, configTarget); } public isTestExecution(): boolean { return process.env.VSC_PYTHON_CI_TEST === '1'; } - private async verifySetting(pythonConfig: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { + private async verifySetting(configSection: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { if (this.isTestExecution()) { let retries = 0; do { - const setting = pythonConfig.inspect(settingName); + const setting = configSection.inspect(settingName); if (!setting && value === undefined) { break; // Both are unset } diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 4ef5f2e80645..b0256848ea22 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -156,7 +156,7 @@ export class FormatterInstaller extends BaseInstaller { const formatterName = ProductNames.get(formatter)!; if (item.endsWith(formatterName)) { - await this.configService.updateSettingAsync('formatting.provider', formatterName, resource); + await this.configService.updateSetting('formatting.provider', formatterName, resource); return this.install(formatter, resource); } } diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index 14655499e958..5d6bf4b4513b 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -28,6 +28,8 @@ import { PersistentStateFactory } from './persistentState'; import { IS_64_BIT, IS_WINDOWS } from './platform/constants'; import { PathUtils } from './platform/pathUtils'; import { CurrentProcess } from './process/currentProcess'; +import { TerminalActivator } from './terminal/activator'; +import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler'; import { Bash } from './terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell @@ -37,7 +39,7 @@ import { TerminalServiceFactory } from './terminal/factory'; import { TerminalHelper } from './terminal/helper'; import { ITerminalActivationCommandProvider, - ITerminalHelper, ITerminalServiceFactory + ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, ITerminalServiceFactory } from './terminal/types'; import { IBrowserService, IConfigurationService, @@ -71,6 +73,8 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IHttpClient, HttpClient); serviceManager.addSingleton(IEditorUtils, EditorUtils); serviceManager.addSingleton(INugetService, NugetService); + serviceManager.addSingleton(ITerminalActivator, TerminalActivator); + serviceManager.addSingleton(ITerminalActivationHandler, PowershellTerminalActivationFailedHandler); serviceManager.addSingleton(ITerminalHelper, TerminalHelper); serviceManager.addSingleton( diff --git a/src/client/common/terminal/activator/base.ts b/src/client/common/terminal/activator/base.ts new file mode 100644 index 000000000000..eef9bc8c970d --- /dev/null +++ b/src/client/common/terminal/activator/base.ts @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Terminal, Uri } from 'vscode'; +import { sleep } from '../../utils/async'; +import { ITerminalActivator, ITerminalHelper, TerminalShellType } from '../types'; + +export class BaseTerminalActivator implements ITerminalActivator { + private readonly activatedTerminals: Set = new Set(); + constructor(private readonly helper: ITerminalHelper) { } + public async activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean = true) { + if (this.activatedTerminals.has(terminal)) { + return false; + } + this.activatedTerminals.add(terminal); + const shellPath = this.helper.getTerminalShellPath(); + const terminalShellType = !shellPath || shellPath.length === 0 ? TerminalShellType.other : this.helper.identifyTerminalShell(shellPath); + + const activationCommamnds = await this.helper.getEnvironmentActivationCommands(terminalShellType, resource); + if (activationCommamnds) { + for (const command of activationCommamnds!) { + terminal.show(preserveFocus); + terminal.sendText(command); + await this.waitForCommandToProcess(terminalShellType); + } + return true; + } else { + return false; + } + } + protected async waitForCommandToProcess(shell: TerminalShellType) { + // Give the command some time to complete. + // Its been observed that sending commands too early will strip some text off in VS Code Terminal. + await sleep(500); + } +} diff --git a/src/client/common/terminal/activator/index.ts b/src/client/common/terminal/activator/index.ts new file mode 100644 index 000000000000..14bb67e710e3 --- /dev/null +++ b/src/client/common/terminal/activator/index.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable, multiInject } from 'inversify'; +import { Terminal, Uri } from 'vscode'; +import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper } from '../types'; +import { BaseTerminalActivator } from './base'; + +@injectable() +export class TerminalActivator implements ITerminalActivator { + protected baseActivator!: ITerminalActivator; + constructor(@inject(ITerminalHelper) readonly helper: ITerminalHelper, + @multiInject(ITerminalActivationHandler) private readonly handlers: ITerminalActivationHandler[]) { + this.initialize(); + } + public async activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean = true) { + const activated = await this.baseActivator.activateEnvironmentInTerminal(terminal, resource, preserveFocus); + this.handlers.forEach(handler => handler.handleActivation(terminal, resource, preserveFocus, activated).ignoreErrors()); + return activated; + } + protected initialize() { + this.baseActivator = new BaseTerminalActivator(this.helper); + } +} diff --git a/src/client/common/terminal/activator/powershellFailedHandler.ts b/src/client/common/terminal/activator/powershellFailedHandler.ts new file mode 100644 index 000000000000..a8ecba9f6da0 --- /dev/null +++ b/src/client/common/terminal/activator/powershellFailedHandler.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable, named } from 'inversify'; +import { Terminal, Uri } from 'vscode'; +import { PowerShellActivationHackDiagnosticsServiceId, PowershellActivationNotAvailableDiagnostic } from '../../../application/diagnostics/checks/powerShellActivation'; +import { IDiagnosticsService } from '../../../application/diagnostics/types'; +import { IPlatformService } from '../../platform/types'; +import { ITerminalActivationHandler, ITerminalHelper, TerminalShellType } from '../types'; + +@injectable() +export class PowershellTerminalActivationFailedHandler implements ITerminalActivationHandler { + constructor(@inject(ITerminalHelper) private readonly helper: ITerminalHelper, + @inject(IPlatformService) private readonly platformService: IPlatformService, + @inject(IDiagnosticsService) @named(PowerShellActivationHackDiagnosticsServiceId) private readonly diagnosticService: IDiagnosticsService) { + } + public async handleActivation(_terminal: Terminal, resource: Uri | undefined, _preserveFocus: boolean, activated: boolean) { + if (activated || !this.platformService.isWindows) { + return; + } + const shell = this.helper.identifyTerminalShell(this.helper.getTerminalShellPath()); + if (shell !== TerminalShellType.powershell && shell !== TerminalShellType.powershellCore) { + return; + } + // Check if we can activate in Command Prompt. + const activationCommands = await this.helper.getEnvironmentActivationCommands(TerminalShellType.commandPrompt, resource); + if (!activationCommands || !Array.isArray(activationCommands) || activationCommands.length === 0) { + return; + } + this.diagnosticService.handle([new PowershellActivationNotAvailableDiagnostic()]).ignoreErrors(); + } + +} diff --git a/src/client/common/terminal/commandPrompt.ts b/src/client/common/terminal/commandPrompt.ts new file mode 100644 index 000000000000..aa4176dd2213 --- /dev/null +++ b/src/client/common/terminal/commandPrompt.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { ConfigurationTarget } from 'vscode'; +import { IConfigurationService, ICurrentProcess } from '../types'; + +export function getCommandPromptLocation(currentProcess: ICurrentProcess) { + // https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts#L218 + // Determine the correct System32 path. We want to point to Sysnative + // when the 32-bit version of VS Code is running on a 64-bit machine. + // The reason for this is because PowerShell's important PSReadline + // module doesn't work if this is not the case. See #27915. + const is32ProcessOn64Windows = currentProcess.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const system32Path = path.join(currentProcess.env.windir!, is32ProcessOn64Windows ? 'Sysnative' : 'System32'); + return path.join(system32Path, 'cmd.exe'); +} +export async function useCommandPromptAsDefaultShell(currentProcess: ICurrentProcess, configService: IConfigurationService) { + const cmdPromptLocation = getCommandPromptLocation(currentProcess); + await configService.updateSectionSetting('terminal', 'integrated.shell.windows', cmdPromptLocation, undefined, ConfigurationTarget.Global); +} diff --git a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts index 98ae7fccce37..92b527030dbc 100644 --- a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts +++ b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts @@ -5,7 +5,6 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { IServiceContainer } from '../../../ioc/types'; import '../../extensions'; -import { IPlatformService } from '../../platform/types'; import { TerminalShellType } from '../types'; import { BaseActivationCommandProvider } from './baseActivationProvider'; @@ -34,18 +33,7 @@ export class CommandPromptAndPowerShell extends BaseActivationCommandProvider { // lets not try to run the powershell file from command prompt (user may not have powershell) return []; } else { - // This means we're in powershell and we have a .bat file. - if (this.serviceContainer.get(IPlatformService).isWindows) { - // On windows, the solution is to go into cmd, then run the batch (.bat) file and go back into powershell. - const powershellExe = targetShell === TerminalShellType.powershell ? 'powershell' : 'pwsh'; - const activationCmd = scriptFile.fileToCommandArgument(); - return [ - `& cmd /k "${activationCmd.replace(/"/g, '""')} & ${powershellExe}"` - ]; - } else { - // Powershell on non-windows os, we cannot execute the batch file. - return; - } + return; } } diff --git a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts index dc75060b4131..ad8470caa6a0 100644 --- a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +'use strict'; + import { injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; @@ -105,19 +107,7 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman envName: string, targetShell: TerminalShellType ): Promise { - // https://github.com/conda/conda/issues/626 - // On windows, the solution is to go into cmd, then run the batch (.bat) file and go back into powershell. - const powershellExe = targetShell === TerminalShellType.powershell ? 'powershell' : 'pwsh'; - const activateCmd = await this.getWindowsActivateCommand(); - - let cmdStyleCmd = `${activateCmd} ${envName.toCommandArgument()}`; - // we need to double-quote any cmd quotes as we will wrap them - // in another layer of quotes for powershell: - cmdStyleCmd = cmdStyleCmd.replace(/"/g, '""'); - - return [ - `& cmd /k "${cmdStyleCmd} & ${powershellExe}"` - ]; + return; } public async getFishCommands( diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index 240b418cac7d..41708be66388 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -9,7 +9,6 @@ import { ITerminalManager, IWorkspaceService } from '../application/types'; import '../extensions'; import { IPlatformService } from '../platform/types'; import { IConfigurationService } from '../types'; -import { sleep } from '../utils/async'; import { CondaActivationCommandProvider } from './environmentActivationProviders/condaActivationProvider'; import { ITerminalActivationCommandProvider, ITerminalHelper, TerminalShellType } from './types'; @@ -30,11 +29,8 @@ const IS_TCSHELL = /(tcsh$)/i; @injectable() export class TerminalHelper implements ITerminalHelper { private readonly detectableShells: Map; - private readonly activatedTerminals: Set; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.detectableShells = new Map(); - this.activatedTerminals = new Set(); this.detectableShells.set(TerminalShellType.powershell, IS_POWERSHELL); this.detectableShells.set(TerminalShellType.gitbash, IS_GITBASH); this.detectableShells.set(TerminalShellType.bash, IS_BASH); @@ -111,26 +107,4 @@ export class TerminalHelper implements ITerminalHelper { } } } - - public async activateEnvironmentInTerminal(terminal: Terminal, preserveFocus: boolean = true, resource?: Uri) { - if (this.activatedTerminals.has(terminal)) { - return; - } - this.activatedTerminals.add(terminal); - const shellPath = this.getTerminalShellPath(); - const terminalShellType = !shellPath || shellPath.length === 0 ? TerminalShellType.other : this.identifyTerminalShell(shellPath); - - const activationCommamnds = await this.getEnvironmentActivationCommands(terminalShellType, resource); - if (activationCommamnds) { - for (const command of activationCommamnds!) { - terminal.show(preserveFocus); - terminal.sendText(command); - - // Give the command some time to complete. - // Its been observed that sending commands too early will strip some text off in VS Terminal. - const delay = (terminalShellType === TerminalShellType.powershell || TerminalShellType.powershellCore) ? 1000 : 500; - await sleep(delay); - } - } - } } diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 41c9ef599032..398af106931b 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -10,7 +10,7 @@ import { captureTelemetry } from '../../telemetry'; import { TERMINAL_CREATE } from '../../telemetry/constants'; import { ITerminalManager } from '../application/types'; import { IConfigurationService, IDisposableRegistry } from '../types'; -import { ITerminalHelper, ITerminalService, TerminalShellType } from './types'; +import { ITerminalActivator, ITerminalHelper, ITerminalService, TerminalShellType } from './types'; @injectable() export class TerminalService implements ITerminalService, Disposable { @@ -19,6 +19,7 @@ export class TerminalService implements ITerminalService, Disposable { private terminalClosed = new EventEmitter(); private terminalManager: ITerminalManager; private terminalHelper: ITerminalHelper; + private terminalActivator: ITerminalActivator; public get onDidCloseTerminal(): Event { return this.terminalClosed.event.bind(this.terminalClosed); } @@ -31,6 +32,7 @@ export class TerminalService implements ITerminalService, Disposable { this.terminalHelper = this.serviceContainer.get(ITerminalHelper); this.terminalManager = this.serviceContainer.get(ITerminalManager); this.terminalManager.onDidCloseTerminal(this.terminalCloseHandler, this, disposableRegistry); + this.terminalActivator = this.serviceContainer.get(ITerminalActivator); } public dispose() { if (this.terminal) { @@ -63,7 +65,7 @@ export class TerminalService implements ITerminalService, Disposable { // Sometimes the terminal takes some time to start up before it can start accepting input. await new Promise(resolve => setTimeout(resolve, 100)); - await this.terminalHelper.activateEnvironmentInTerminal(this.terminal!, preserveFocus, this.resource); + await this.terminalActivator.activateEnvironmentInTerminal(this.terminal!, this.resource, preserveFocus); this.terminal!.show(preserveFocus); diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index fc342ce484c4..aebf8758b7fc 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -49,7 +49,11 @@ export interface ITerminalHelper { getTerminalShellPath(): string; buildCommandForTerminal(terminalShellType: TerminalShellType, command: string, args: string[]): string; getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri): Promise; - activateEnvironmentInTerminal(terminal: Terminal, preserveFocus?: boolean, resource?: Uri): Promise; +} + +export const ITerminalActivator = Symbol('ITerminalActivator'); +export interface ITerminalActivator { + activateEnvironmentInTerminal(terminal: Terminal, resource: Uri | undefined, preserveFocus?: boolean): Promise; } export const ITerminalActivationCommandProvider = Symbol('ITerminalActivationCommandProvider'); @@ -59,3 +63,8 @@ export interface ITerminalActivationCommandProvider { getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise; getActivationCommandsForInterpreter?(pythonPath, targetShell: TerminalShellType): Promise; } + +export const ITerminalActivationHandler = Symbol('ITerminalActivationHandler'); +export interface ITerminalActivationHandler { + handleActivation(terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean, activated: boolean): Promise; +} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 355e547ff45e..9b4db1295f37 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -264,7 +264,8 @@ export const IConfigurationService = Symbol('IConfigurationService'); export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; - updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; + updateSetting(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; + updateSectionSetting(section: string, setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; } export const ISocketServer = Symbol('ISocketServer'); diff --git a/src/client/languageServices/proposeLanguageServerBanner.ts b/src/client/languageServices/proposeLanguageServerBanner.ts index 54bb1afce597..c58c4697a114 100644 --- a/src/client/languageServices/proposeLanguageServerBanner.ts +++ b/src/client/languageServices/proposeLanguageServerBanner.ts @@ -119,6 +119,6 @@ export class ProposeLanguageServerBanner implements IPythonExtensionBanner { } public async enableNewLanguageServer(): Promise { - await this.configuration.updateSettingAsync('jediEnabled', false, undefined, ConfigurationTarget.Global); + await this.configuration.updateSetting('jediEnabled', false, undefined, ConfigurationTarget.Global); } } diff --git a/src/client/linters/linterInfo.ts b/src/client/linters/linterInfo.ts index 12f3e1835453..055d76311d39 100644 --- a/src/client/linters/linterInfo.ts +++ b/src/client/linters/linterInfo.ts @@ -38,7 +38,7 @@ export class LinterInfo implements ILinterInfo { } public async enableAsync(enabled: boolean, resource?: Uri): Promise { - return this.configService.updateSettingAsync(`linting.${this.enabledSettingName}`, enabled, resource); + return this.configService.updateSetting(`linting.${this.enabledSettingName}`, enabled, resource); } public isEnabled(resource?: Uri): boolean { const settings = this.configService.getSettings(resource); diff --git a/src/client/linters/linterManager.ts b/src/client/linters/linterManager.ts index 362ca4bf21fd..c07d0b48e037 100644 --- a/src/client/linters/linterManager.ts +++ b/src/client/linters/linterManager.ts @@ -64,7 +64,7 @@ export class LinterManager implements ILinterManager { } public async enableLintingAsync(enable: boolean, resource?: Uri): Promise { - await this.configService.updateSettingAsync(`linting.${this.lintingEnabledSettingName}`, enable, resource); + await this.configService.updateSetting(`linting.${this.lintingEnabledSettingName}`, enable, resource); // If nothing is enabled, fix it up to PyLint (default). if (enable && this.getActiveLinters(resource).length === 0) { diff --git a/src/client/terminals/activation.ts b/src/client/terminals/activation.ts index f440f5f8fd9c..131840c83489 100644 --- a/src/client/terminals/activation.ts +++ b/src/client/terminals/activation.ts @@ -6,16 +6,15 @@ import { inject, injectable } from 'inversify'; import { Disposable, Terminal } from 'vscode'; import { ITerminalManager } from '../common/application/types'; -import { ITerminalHelper } from '../common/terminal/types'; +import { ITerminalActivator } from '../common/terminal/types'; import { IDisposableRegistry } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { ITerminalAutoActivation } from './types'; @injectable() export class TerminalAutoActivation implements ITerminalAutoActivation { - private readonly helper: ITerminalHelper; - constructor(@inject(IServiceContainer) private container: IServiceContainer) { - this.helper = container.get(ITerminalHelper); + constructor(@inject(IServiceContainer) private container: IServiceContainer, + @inject(ITerminalActivator) private readonly activator: ITerminalActivator) { } public register() { const manager = this.container.get(ITerminalManager); @@ -23,7 +22,7 @@ export class TerminalAutoActivation implements ITerminalAutoActivation { const disposable = manager.onDidOpenTerminal(this.activateTerminal, this); disposables.push(disposable); } - private activateTerminal(terminal: Terminal): Promise { - return this.helper.activateEnvironmentInTerminal(terminal); + private async activateTerminal(terminal: Terminal): Promise { + await this.activator.activateEnvironmentInTerminal(terminal, undefined); } } diff --git a/src/client/unittests/display/main.ts b/src/client/unittests/display/main.ts index 9e4f2fb53d71..b9dfe431bddb 100644 --- a/src/client/unittests/display/main.ts +++ b/src/client/unittests/display/main.ts @@ -148,7 +148,7 @@ export class TestResultDisplay implements ITestResultDisplay { 'unitTest.unittestEnabled', 'unitTest.nosetestsEnabled']; for (const setting of settingsToDisable) { - await configurationService.updateSettingAsync(setting, false).catch(noop); + await configurationService.updateSetting(setting, false).catch(noop); } } diff --git a/src/test/activation/excludeFiles.ls.test.ts b/src/test/activation/excludeFiles.ls.test.ts index 288a052d3adb..d80b3a48a958 100644 --- a/src/test/activation/excludeFiles.ls.test.ts +++ b/src/test/activation/excludeFiles.ls.test.ts @@ -52,7 +52,7 @@ suite('Exclude files (Language Server)', () => { } async function setSetting(name: string, value: {} | undefined): Promise { - await configService.updateSettingAsync(name, value, undefined, ConfigurationTarget.Global); + await configService.updateSetting(name, value, undefined, ConfigurationTarget.Global); } test('Default exclusions', async () => { diff --git a/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts b/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts new file mode 100644 index 000000000000..f0d63aa587b0 --- /dev/null +++ b/src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { PowerShellActivationHackDiagnosticsService } from '../../../../client/application/diagnostics/checks/powerShellActivation'; +import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; +import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from '../../../../client/application/diagnostics/types'; +import { IApplicationEnvironment } from '../../../../client/common/application/types'; +import { IPlatformService } from '../../../../client/common/platform/types'; +import { ICurrentProcess, IPathUtils } from '../../../../client/common/types'; +import { EnvironmentVariables } from '../../../../client/common/variables/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +// tslint:disable-next-line:max-func-body-length +suite('Application Diagnostics - PowerShell Activation', () => { + let diagnosticService: IDiagnosticsService; + let platformService: typemoq.IMock; + let messageHandler: typemoq.IMock>; + let filterService: typemoq.IMock; + let procEnv: typemoq.IMock; + let appEnv: typemoq.IMock; + let commandFactory: typemoq.IMock; + const pathVariableName = 'Path'; + const pathDelimiter = ';'; + const extensionName = 'Some Extension Name'; + setup(() => { + const serviceContainer = typemoq.Mock.ofType(); + platformService = typemoq.Mock.ofType(); + platformService.setup(p => p.pathVariableName).returns(() => pathVariableName); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPlatformService))) + .returns(() => platformService.object); + + messageHandler = typemoq.Mock.ofType>(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticHandlerService), typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId))) + .returns(() => messageHandler.object); + + appEnv = typemoq.Mock.ofType(); + appEnv.setup(a => a.extensionName).returns(() => extensionName); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationEnvironment))) + .returns(() => appEnv.object); + + filterService = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticFilterService))) + .returns(() => filterService.object); + + commandFactory = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) + .returns(() => commandFactory.object); + + const currentProc = typemoq.Mock.ofType(); + procEnv = typemoq.Mock.ofType(); + currentProc.setup(p => p.env).returns(() => procEnv.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(ICurrentProcess))) + .returns(() => currentProc.object); + + const pathUtils = typemoq.Mock.ofType(); + pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPathUtils))) + .returns(() => pathUtils.object); + + diagnosticService = new PowerShellActivationHackDiagnosticsService(serviceContainer.object); + }); + + test('Can handle PowerShell diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic.setup(d => d.code) + .returns(() => DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + expect(canHandle).to.be.equal(true, 'Invalid value'); + diagnostic.verifyAll(); + }); + test('Can not handle non-EnvPathVariable diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic.setup(d => d.code) + .returns(() => 'Something Else') + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + expect(canHandle).to.be.equal(false, 'Invalid value'); + diagnostic.verifyAll(); + }); + test('Must return empty diagnostics', async () => { + const diagnostics = await diagnosticService.diagnose(); + expect(diagnostics).to.be.deep.equal([]); + }); + test('Should display three options in message displayed with 4 commands', async () => { + const diagnostic = typemoq.Mock.ofType(); + let options: MessageCommandPrompt | undefined; + diagnostic.setup(d => d.code) + .returns(() => DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + const alwaysIgnoreCommand = typemoq.Mock.ofType(); + commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'ignore', options: DiagnosticScope.Global }))) + .returns(() => alwaysIgnoreCommand.object) + .verifiable(typemoq.Times.once()); + const launchBrowserCommand = typemoq.Mock.ofType(); + commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'launch' }))) + .returns(() => launchBrowserCommand.object) + .verifiable(typemoq.Times.once()); + messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) + .callback((_, opts: MessageCommandPrompt) => options = opts) + .verifiable(typemoq.Times.once()); + + await diagnosticService.handle([diagnostic.object]); + + diagnostic.verifyAll(); + commandFactory.verifyAll(); + messageHandler.verifyAll(); + expect(options!.commandPrompts).to.be.lengthOf(4); + expect(options!.commandPrompts[0].prompt).to.be.equal('Use Command Prompt'); + }); +}); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index ef7ace6d5b5b..1fe59e8f5f46 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -116,7 +116,7 @@ suite('Module Installer', () => { } async function resetSettings(): Promise { const configService = ioc.serviceManager.get(IConfigurationService); - await configService.updateSettingAsync('linting.pylintEnabled', true, rootWorkspaceUri, ConfigurationTarget.Workspace); + await configService.updateSetting('linting.pylintEnabled', true, rootWorkspaceUri, ConfigurationTarget.Workspace); } async function getCurrentPythonPath(): Promise { const pythonPath = PythonSettings.getInstance(workspaceUri).pythonPath; diff --git a/src/test/common/process/pythonProc.simple.multiroot.test.ts b/src/test/common/process/pythonProc.simple.multiroot.test.ts index b19fe80f80d0..be80d75f64e8 100644 --- a/src/test/common/process/pythonProc.simple.multiroot.test.ts +++ b/src/test/common/process/pythonProc.simple.multiroot.test.ts @@ -68,7 +68,7 @@ suite('PythonExecutableService', () => { configService = serviceManager.get(IConfigurationService); pythonExecFactory = serviceContainer.get(IPythonExecutionFactory); - await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); + await configService.updateSetting('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); return initializeTest(); }); suiteTeardown(closeActiveWindows); @@ -77,12 +77,12 @@ suite('PythonExecutableService', () => { cont.unload(); await closeActiveWindows(); await clearPythonPathInWorkspaceFolder(workspace4Path); - await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); + await configService.updateSetting('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); await initializeTest(); }); test('Importing without a valid PYTHONPATH should fail', async () => { - await configService.updateSettingAsync('envFile', 'someInvalidFile.env', workspace4PyFile, ConfigurationTarget.WorkspaceFolder); + await configService.updateSetting('envFile', 'someInvalidFile.env', workspace4PyFile, ConfigurationTarget.WorkspaceFolder); pythonExecFactory = serviceContainer.get(IPythonExecutionFactory); const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const promise = pythonExecService.exec([workspace4PyFile.fsPath], { cwd: path.dirname(workspace4PyFile.fsPath), throwOnStdErr: true }); @@ -91,7 +91,7 @@ suite('PythonExecutableService', () => { }); test('Importing with a valid PYTHONPATH from .env file should succeed', async () => { - await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); + await configService.updateSetting('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const promise = pythonExecService.exec([workspace4PyFile.fsPath], { cwd: path.dirname(workspace4PyFile.fsPath), throwOnStdErr: true }); diff --git a/src/test/common/terminals/activation.commandPrompt.unit.test.ts b/src/test/common/terminals/activation.commandPrompt.unit.test.ts index 556cb8e212be..63e3fa23468f 100644 --- a/src/test/common/terminals/activation.commandPrompt.unit.test.ts +++ b/src/test/common/terminals/activation.commandPrompt.unit.test.ts @@ -96,7 +96,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { expect(commands).to.be.deep.equal([pathToScriptFile.fileToCommandArgument()], 'Invalid command'); }); - test('Ensure batch files are supported by powershell (on windows)', async () => { + test('Ensure batch files are not supported by powershell (on windows)', async () => { const batch = new CommandPromptAndPowerShell(serviceContainer.object); platform.setup(p => p.isWindows).returns(() => true); @@ -104,14 +104,10 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await batch.getActivationCommands(resource, TerminalShellType.powershell); - // Executing batch files from powershell requires going back to cmd, then into powershell - - const activationCommand = pathToScriptFile.fileToCommandArgument(); - const commands = [`& cmd /k "${activationCommand.replace(/"/g, '""')} & powershell"`]; - expect(command).to.be.deep.equal(commands, 'Invalid command'); + expect(command).to.be.equal(undefined, 'Invalid'); }); - test('Ensure batch files are supported by powershell core (on windows)', async () => { + test('Ensure batch files are not supported by powershell core (on windows)', async () => { const bash = new CommandPromptAndPowerShell(serviceContainer.object); platform.setup(p => p.isWindows).returns(() => true); @@ -119,11 +115,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershellCore); - // Executing batch files from powershell requires going back to cmd, then into powershell - - const activationCommand = pathToScriptFile.fileToCommandArgument(); - const commands = [`& cmd /k "${activationCommand.replace(/"/g, '""')} & pwsh"`]; - expect(command).to.be.deep.equal(commands, 'Invalid command'); + expect(command).to.be.equal(undefined, 'Invalid'); }); test('Ensure batch files are not supported by powershell (on non-windows)', async () => { diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index 7b94593f06b3..3a906d1b79c2 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -164,9 +164,7 @@ suite('Terminal Environment Activation conda', () => { switch (shellType) { case TerminalShellType.powershell: case TerminalShellType.powershellCore: { - const powershellExe = shellType === TerminalShellType.powershell ? 'powershell' : 'pwsh'; - const envNameForCmd = envName.toCommandArgument().replace(/"/g, '""'); - expectedActivationCommamnd = isWindows ? [`& cmd /k \"activate ${envNameForCmd} & ${powershellExe}\"`] : undefined; + expectedActivationCommamnd = undefined; break; } case TerminalShellType.fish: { @@ -178,7 +176,11 @@ suite('Terminal Environment Activation conda', () => { break; } } - expect(activationCommands).to.deep.equal(expectedActivationCommamnd, 'Incorrect Activation command'); + if (expectedActivationCommamnd) { + expect(activationCommands).to.deep.equal(expectedActivationCommamnd, 'Incorrect Activation command'); + } else { + expect(activationCommands).to.equal(undefined, 'Incorrect Activation command'); + } } getNamesAndValues(TerminalShellType).forEach(shellType => { test(`Conda activation command for shell ${shellType.name} on (windows)`, async () => { @@ -323,7 +325,7 @@ suite('Terminal Environment Activation conda', () => { testName: string; basePath: string; envName: string; - expectedResult: string[]; + expectedResult: string[] | undefined; expectedRawCmd: string; terminalKind: TerminalShellType; }; @@ -334,7 +336,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path on windows for powershell', basePath: windowsTestPath, envName: 'TesterEnv', - expectedResult: [`& cmd /k "${path.join(windowsTestPath, 'activate')} TesterEnv & powershell"`], + expectedResult: undefined, expectedRawCmd: `${path.join(windowsTestPath, 'activate')}`, terminalKind: TerminalShellType.powershell }, @@ -342,7 +344,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path with spaces on windows for powershell', basePath: windowsTestPathSpaces, envName: 'TesterEnv', - expectedResult: [`& cmd /k """${path.join(windowsTestPathSpaces, 'activate')}"" TesterEnv & powershell"`], + expectedResult: undefined, expectedRawCmd: `"${path.join(windowsTestPathSpaces, 'activate')}"`, terminalKind: TerminalShellType.powershell }, @@ -350,7 +352,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path on windows under powershell, environment name has spaces', basePath: windowsTestPath, envName: 'The Tester Environment', - expectedResult: [`& cmd /k "${path.join(windowsTestPath, 'activate')} ""The Tester Environment"" & powershell"`], + expectedResult: undefined, expectedRawCmd: `${path.join(windowsTestPath, 'activate')}`, terminalKind: TerminalShellType.powershell }, @@ -358,7 +360,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path on windows for powershell-core', basePath: windowsTestPath, envName: 'TesterEnv', - expectedResult: [`& cmd /k "${path.join(windowsTestPath, 'activate')} TesterEnv & pwsh"`], + expectedResult: undefined, expectedRawCmd: `${path.join(windowsTestPath, 'activate')}`, terminalKind: TerminalShellType.powershellCore }, @@ -366,7 +368,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path with spaces on windows for powershell-core', basePath: windowsTestPathSpaces, envName: 'TesterEnv', - expectedResult: [`& cmd /k """${path.join(windowsTestPathSpaces, 'activate')}"" TesterEnv & pwsh"`], + expectedResult: undefined, expectedRawCmd: `"${path.join(windowsTestPathSpaces, 'activate')}"`, terminalKind: TerminalShellType.powershellCore }, @@ -374,7 +376,7 @@ suite('Terminal Environment Activation conda', () => { testName: 'Activation uses full path on windows for powershell-core, environment name has spaces', basePath: windowsTestPath, envName: 'The Tester Environment', - expectedResult: [`& cmd /k "${path.join(windowsTestPath, 'activate')} ""The Tester Environment"" & pwsh"`], + expectedResult: undefined, expectedRawCmd: `${path.join(windowsTestPath, 'activate')}`, terminalKind: TerminalShellType.powershellCore }, diff --git a/src/test/common/terminals/activation.unit.test.ts b/src/test/common/terminals/activation.unit.test.ts index 5931871ae6a5..91ad4dbe55f8 100644 --- a/src/test/common/terminals/activation.unit.test.ts +++ b/src/test/common/terminals/activation.unit.test.ts @@ -6,7 +6,7 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { Terminal } from 'vscode'; import { ITerminalManager } from '../../../client/common/application/types'; -import { ITerminalHelper } from '../../../client/common/terminal/types'; +import { ITerminalActivator, ITerminalHelper } from '../../../client/common/terminal/types'; import { IDisposableRegistry } from '../../../client/common/types'; import { noop } from '../../../client/common/utils/misc'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -14,13 +14,13 @@ import { TerminalAutoActivation } from '../../../client/terminals/activation'; import { ITerminalAutoActivation } from '../../../client/terminals/types'; suite('Terminal Auto Activation', () => { - let helper: TypeMoq.IMock; + let activator: TypeMoq.IMock; let terminalManager: TypeMoq.IMock; let terminalAutoActivation: ITerminalAutoActivation; setup(() => { terminalManager = TypeMoq.Mock.ofType(); - helper = TypeMoq.Mock.ofType(); + activator = TypeMoq.Mock.ofType(); const disposables = []; const serviceContainer = TypeMoq.Mock.ofType(); @@ -29,12 +29,12 @@ suite('Terminal Auto Activation', () => { .returns(() => terminalManager.object); serviceContainer .setup(c => c.get(TypeMoq.It.isValue(ITerminalHelper), TypeMoq.It.isAny())) - .returns(() => helper.object); + .returns(() => activator.object); serviceContainer .setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())) .returns(() => disposables); - terminalAutoActivation = new TerminalAutoActivation(serviceContainer.object); + terminalAutoActivation = new TerminalAutoActivation(serviceContainer.object, activator.object); }); test('New Terminals should be activated', async () => { @@ -46,7 +46,7 @@ suite('Terminal Auto Activation', () => { eventHandler = handler; return { dispose: noop }; }); - helper + activator .setup(h => h.activateEnvironmentInTerminal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .verifiable(TypeMoq.Times.once()); @@ -56,6 +56,6 @@ suite('Terminal Auto Activation', () => { eventHandler!.bind(terminalAutoActivation)(terminal.object); - helper.verifyAll(); + activator.verifyAll(); }); }); diff --git a/src/test/common/terminals/activator/base.unit.test.ts b/src/test/common/terminals/activator/base.unit.test.ts new file mode 100644 index 000000000000..4e81bb9e39b4 --- /dev/null +++ b/src/test/common/terminals/activator/base.unit.test.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as TypeMoq from 'typemoq'; +import { Terminal } from 'vscode'; +import { BaseTerminalActivator } from '../../../../client/common/terminal/activator/base'; +import { ITerminalActivator, ITerminalHelper } from '../../../../client/common/terminal/types'; +import { noop } from '../../../../client/common/utils/misc'; + +// tslint:disable-next-line:max-func-body-length +suite('Terminalx Base Activator', () => { + let activator: ITerminalActivator; + let helper: TypeMoq.IMock; + + setup(() => { + helper = TypeMoq.Mock.ofType(); + activator = new class extends BaseTerminalActivator { + public waitForCommandToProcess() { noop(); return Promise.resolve(); } + }(helper.object); + }); + [ + { commandCount: 1, preserveFocus: false }, + { commandCount: 2, preserveFocus: false }, + { commandCount: 1, preserveFocus: true }, + { commandCount: 1, preserveFocus: true } + ].forEach(item => { + const titleSuffix = `(${item.commandCount} activation command, and preserve focus in terminal is ${item.preserveFocus})`; + const activationCommands = item.commandCount === 1 ? ['CMD1'] : ['CMD1', 'CMD2']; + test(`Terminal is activated ${titleSuffix}`, async () => { + helper.setup(h => h.getTerminalShellPath()).returns(() => ''); + helper.setup(h => h.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(activationCommands)); + const terminal = TypeMoq.Mock.ofType(); + + terminal + .setup(t => t.show(TypeMoq.It.isValue(item.preserveFocus))) + .returns(() => undefined) + .verifiable(TypeMoq.Times.exactly(activationCommands.length)); + activationCommands.forEach(cmd => { + terminal + .setup(t => t.sendText(TypeMoq.It.isValue(cmd))) + .returns(() => undefined) + .verifiable(TypeMoq.Times.exactly(1)); + }); + + await activator.activateEnvironmentInTerminal(terminal.object, undefined, item.preserveFocus); + + terminal.verifyAll(); + }); + test(`Terminal is activated only once ${titleSuffix}`, async () => { + helper.setup(h => h.getTerminalShellPath()).returns(() => ''); + helper.setup(h => h.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(activationCommands)); + const terminal = TypeMoq.Mock.ofType(); + + terminal + .setup(t => t.show(TypeMoq.It.isValue(item.preserveFocus))) + .returns(() => undefined) + .verifiable(TypeMoq.Times.exactly(activationCommands.length)); + activationCommands.forEach(cmd => { + terminal + .setup(t => t.sendText(TypeMoq.It.isValue(cmd))) + .returns(() => undefined) + .verifiable(TypeMoq.Times.exactly(1)); + }); + + await activator.activateEnvironmentInTerminal(terminal.object, undefined, item.preserveFocus); + await activator.activateEnvironmentInTerminal(terminal.object, undefined, item.preserveFocus); + await activator.activateEnvironmentInTerminal(terminal.object, undefined, item.preserveFocus); + + terminal.verifyAll(); + }); + }); +}); diff --git a/src/test/common/terminals/activator/index.unit.test.ts b/src/test/common/terminals/activator/index.unit.test.ts new file mode 100644 index 000000000000..91d1766b1a74 --- /dev/null +++ b/src/test/common/terminals/activator/index.unit.test.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as TypeMoq from 'typemoq'; +import { Terminal } from 'vscode'; +import { TerminalActivator } from '../../../../client/common/terminal/activator'; +import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper } from '../../../../client/common/terminal/types'; + +// tslint:disable-next-line:max-func-body-length +suite('Terminal Activator', () => { + let activator: TerminalActivator; + let baseActivator: TypeMoq.IMock; + let handler1: TypeMoq.IMock; + let handler2: TypeMoq.IMock; + + setup(() => { + baseActivator = TypeMoq.Mock.ofType(); + handler1 = TypeMoq.Mock.ofType(); + handler2 = TypeMoq.Mock.ofType(); + activator = new class extends TerminalActivator { + protected initialize() { + this.baseActivator = baseActivator.object; + } + }(TypeMoq.Mock.ofType().object, [handler1.object, handler2.object]); + }); + async function testActivationAndHandlers(activationSuccessful: boolean) { + baseActivator + .setup(b => b.activateEnvironmentInTerminal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(activationSuccessful)) + .verifiable(TypeMoq.Times.once()); + handler1.setup(h => h.handleActivation(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isValue(activationSuccessful))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + handler2.setup(h => h.handleActivation(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isValue(activationSuccessful))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + const terminal = TypeMoq.Mock.ofType(); + await activator.activateEnvironmentInTerminal(terminal.object, undefined, activationSuccessful); + + baseActivator.verifyAll(); + handler1.verifyAll(); + handler2.verifyAll(); + } + test('Terminal is activated and handlers are invoked', () => testActivationAndHandlers(true)); + test('Terminal is not activated and handlers are invoked', () => testActivationAndHandlers(false)); +}); diff --git a/src/test/common/terminals/activator/powerShellFailedHandler.unit.test.ts b/src/test/common/terminals/activator/powerShellFailedHandler.unit.test.ts new file mode 100644 index 000000000000..1711af2988f4 --- /dev/null +++ b/src/test/common/terminals/activator/powerShellFailedHandler.unit.test.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as TypeMoq from 'typemoq'; +import { Terminal } from 'vscode'; +import { IDiagnosticsService } from '../../../../client/application/diagnostics/types'; +import { IPlatformService } from '../../../../client/common/platform/types'; +import { PowershellTerminalActivationFailedHandler } from '../../../../client/common/terminal/activator/powershellFailedHandler'; +import { ITerminalActivationHandler, ITerminalHelper, TerminalShellType } from '../../../../client/common/terminal/types'; +import { getNamesAndValues } from '../../../../client/common/utils/enum'; + +// tslint:disable-next-line:max-func-body-length +suite('Terminal Activation Powershell Failed Handler', () => { + let psHandler: ITerminalActivationHandler; + let helper: TypeMoq.IMock; + let platform: TypeMoq.IMock; + let diagnosticService: TypeMoq.IMock; + + async function testDiagnostics(mustHandleDiagnostics: boolean, isWindows: boolean, activatedSuccessfully: boolean, shellType: TerminalShellType, cmdPromptHasActivationCommands: boolean) { + platform.setup(p => p.isWindows).returns(() => isWindows); + helper + .setup(p => p.getTerminalShellPath()) + .returns(() => ''); + // .verifiable(TypeMoq.Times.atMostOnce()); + helper + .setup(p => p.identifyTerminalShell(TypeMoq.It.isAny())) + .returns(() => shellType); + // .verifiable(TypeMoq.Times.atMostOnce());c + const cmdPromptCommands = cmdPromptHasActivationCommands ? ['a'] : []; + helper.setup(h => h.getEnvironmentActivationCommands(TypeMoq.It.isValue(TerminalShellType.commandPrompt), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(cmdPromptCommands)); + // .verifiable(TypeMoq.Times.atMostOnce()); + + diagnosticService + .setup(d => d.handle(TypeMoq.It.isAny())) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.exactly(mustHandleDiagnostics ? 1 : 0)); + await psHandler.handleActivation(TypeMoq.Mock.ofType().object, undefined, false, activatedSuccessfully); + } + + [true, false].forEach(isWindows => { + suite(`OS is ${isWindows ? 'Windows' : 'Non-Widows'}`, () => { + getNamesAndValues(TerminalShellType).forEach(shell => { + suite(`Shell is ${shell.name}`, () => { + [true, false].forEach(hasCommandPromptActivations => { + hasCommandPromptActivations = isWindows && hasCommandPromptActivations && shell.value !== TerminalShellType.commandPrompt; + suite(`${hasCommandPromptActivations ? 'Can activate with Command Prompt' : 'Can\'t activate with Command Prompt'}`, () => { + [true, false].forEach(activatedSuccessfully => { + suite(`Terminal Activation is ${activatedSuccessfully ? 'successful' : 'has failed'}`, () => { + setup(() => { + helper = TypeMoq.Mock.ofType(); + platform = TypeMoq.Mock.ofType(); + diagnosticService = TypeMoq.Mock.ofType(); + psHandler = new PowershellTerminalActivationFailedHandler(helper.object, platform.object, diagnosticService.object); + }); + const isPs = shell.value === TerminalShellType.powershell || shell.value === TerminalShellType.powershellCore; + const mustHandleDiagnostics = isPs && !activatedSuccessfully && hasCommandPromptActivations; + test(`Diagnostic must ${mustHandleDiagnostics ? 'be' : 'not be'} handled`, async () => { + await testDiagnostics(mustHandleDiagnostics, isWindows, activatedSuccessfully, shell.value, hasCommandPromptActivations); + helper.verifyAll(); + diagnosticService.verifyAll(); + }); + }); + }); + }); + }); + }); + }); + }); + }); +}); diff --git a/src/test/common/terminals/commandPrompt.unit.test.ts b/src/test/common/terminals/commandPrompt.unit.test.ts new file mode 100644 index 000000000000..9c99fcf4759c --- /dev/null +++ b/src/test/common/terminals/commandPrompt.unit.test.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { ConfigurationTarget } from 'vscode'; +import { getCommandPromptLocation, useCommandPromptAsDefaultShell } from '../../../client/common/terminal/commandPrompt'; +import { IConfigurationService, ICurrentProcess } from '../../../client/common/types'; + +suite('Terminal Command Prompt', () => { + let currentProc: TypeMoq.IMock; + let configService: TypeMoq.IMock; + + setup(() => { + currentProc = TypeMoq.Mock.ofType(); + configService = TypeMoq.Mock.ofType(); + }); + + test('Getting Path Command Prompt executable (32 on 64Win)', async () => { + const env = { windir: 'windir' }; + currentProc.setup(p => p.env) + .returns(() => env) + .verifiable(TypeMoq.Times.atLeastOnce()); + + const cmdPath = getCommandPromptLocation(currentProc.object); + + expect(cmdPath).to.be.deep.equal(path.join('windir', 'System32', 'cmd.exe')); + currentProc.verifyAll(); + }); + test('Getting Path Command Prompt executable (not 32 on 64Win)', async () => { + const env = { PROCESSOR_ARCHITEW6432: 'x', windir: 'windir' }; + currentProc.setup(p => p.env) + .returns(() => env) + .verifiable(TypeMoq.Times.atLeastOnce()); + + const cmdPath = getCommandPromptLocation(currentProc.object); + + expect(cmdPath).to.be.deep.equal(path.join('windir', 'Sysnative', 'cmd.exe')); + currentProc.verifyAll(); + }); + test('Use command prompt as default shell', async () => { + const env = { windir: 'windir' }; + currentProc.setup(p => p.env) + .returns(() => env) + .verifiable(TypeMoq.Times.atLeastOnce()); + const cmdPromptPath = path.join('windir', 'System32', 'cmd.exe'); + configService + .setup(c => c.updateSectionSetting(TypeMoq.It.isValue('terminal'), TypeMoq.It.isValue('integrated.shell.windows'), + TypeMoq.It.isValue(cmdPromptPath), TypeMoq.It.isAny(), + TypeMoq.It.isValue(ConfigurationTarget.Global))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + await useCommandPromptAsDefaultShell(currentProc.object, configService.object); + configService.verifyAll(); + currentProc.verifyAll(); + }); +}); diff --git a/src/test/common/terminals/helper.activation.unit.test.ts b/src/test/common/terminals/helper.activation.unit.test.ts index 899b9d68a141..380085b05f50 100644 --- a/src/test/common/terminals/helper.activation.unit.test.ts +++ b/src/test/common/terminals/helper.activation.unit.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; -import { Disposable, Terminal } from 'vscode'; +import { Disposable } from 'vscode'; import { ITerminalManager, IWorkspaceService } from '../../../client/common/application/types'; import { IPlatformService } from '../../../client/common/platform/types'; import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash'; @@ -74,61 +74,6 @@ suite('Terminal Service helpers', () => { expect(commands).to.equal(undefined, 'Activation command should be undefined if terminal type cannot be determined'); }); - [ - { commandCount: 1, preserveFocus: false }, - { commandCount: 2, preserveFocus: false }, - { commandCount: 1, preserveFocus: true }, - { commandCount: 1, preserveFocus: true } - ].forEach(item => { - const titleSuffix = `(${item.commandCount} activation command, and preserve focus in terminal is ${item.preserveFocus})`; - const activationCommands = item.commandCount === 1 ? ['CMD1'] : ['CMD1', 'CMD2']; - test(`Terminal is activated ${titleSuffix}`, async function () { - // tslint:disable-next-line:no-invalid-this - this.timeout(10000); // We have delays in place to account for issues with VSC Terminal. - helper.getEnvironmentActivationCommands = (_shellType, _resource) => Promise.resolve(activationCommands); - helper.getTerminalShellPath = () => ''; - const terminal = TypeMoq.Mock.ofType(); - - terminal - .setup(t => t.show(TypeMoq.It.isValue(item.preserveFocus))) - .returns(() => undefined) - .verifiable(TypeMoq.Times.exactly(activationCommands.length)); - activationCommands.forEach(cmd => { - terminal - .setup(t => t.sendText(TypeMoq.It.isValue(cmd))) - .returns(() => undefined) - .verifiable(TypeMoq.Times.exactly(1)); - }); - - await helper.activateEnvironmentInTerminal(terminal.object, item.preserveFocus); - - terminal.verifyAll(); - }); - test(`Terminal is activated only once ${titleSuffix}`, async function () { - // tslint:disable-next-line:no-invalid-this - this.timeout(10000); // We have delays in place to account for issues with VSC Terminal. - helper.getEnvironmentActivationCommands = (_shellType, _resource) => Promise.resolve(activationCommands); - helper.getTerminalShellPath = () => ''; - const terminal = TypeMoq.Mock.ofType(); - - terminal - .setup(t => t.show(TypeMoq.It.isValue(item.preserveFocus))) - .returns(() => undefined) - .verifiable(TypeMoq.Times.exactly(activationCommands.length)); - activationCommands.forEach(cmd => { - terminal - .setup(t => t.sendText(TypeMoq.It.isValue(cmd))) - .returns(() => undefined) - .verifiable(TypeMoq.Times.exactly(1)); - }); - - await helper.activateEnvironmentInTerminal(terminal.object, item.preserveFocus); - await helper.activateEnvironmentInTerminal(terminal.object, item.preserveFocus); - await helper.activateEnvironmentInTerminal(terminal.object, item.preserveFocus); - - terminal.verifyAll(); - }); - }); }); getNamesAndValues(TerminalShellType).forEach(terminalShell => { diff --git a/src/test/common/terminals/service.unit.test.ts b/src/test/common/terminals/service.unit.test.ts index e25861c73d34..24814c12decb 100644 --- a/src/test/common/terminals/service.unit.test.ts +++ b/src/test/common/terminals/service.unit.test.ts @@ -7,7 +7,7 @@ import { Disposable, Terminal as VSCodeTerminal, WorkspaceConfiguration } from ' import { ITerminalManager, IWorkspaceService } from '../../../client/common/application/types'; import { IPlatformService } from '../../../client/common/platform/types'; import { TerminalService } from '../../../client/common/terminal/service'; -import { ITerminalHelper, TerminalShellType } from '../../../client/common/terminal/types'; +import { ITerminalActivator, ITerminalHelper, TerminalShellType } from '../../../client/common/terminal/types'; import { IDisposableRegistry } from '../../../client/common/types'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -17,6 +17,7 @@ suite('Terminal Service', () => { let terminal: TypeMoq.IMock; let terminalManager: TypeMoq.IMock; let terminalHelper: TypeMoq.IMock; + let terminalActivator: TypeMoq.IMock; let platformService: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; let disposables: Disposable[] = []; @@ -27,6 +28,7 @@ suite('Terminal Service', () => { platformService = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); terminalHelper = TypeMoq.Mock.ofType(); + terminalActivator = TypeMoq.Mock.ofType(); disposables = []; mockServiceContainer = TypeMoq.Mock.ofType(); @@ -35,6 +37,7 @@ suite('Terminal Service', () => { mockServiceContainer.setup(c => c.get(IPlatformService)).returns(() => platformService.object); mockServiceContainer.setup(c => c.get(IDisposableRegistry)).returns(() => disposables); mockServiceContainer.setup(c => c.get(IWorkspaceService)).returns(() => workspaceService.object); + mockServiceContainer.setup(c => c.get(ITerminalActivator)).returns(() => terminalActivator.object); }); teardown(() => { if (service) { @@ -129,9 +132,9 @@ suite('Terminal Service', () => { terminalHelper .setup(h => h.getTerminalShellPath()).returns(() => '') .verifiable(TypeMoq.Times.once()); - terminalHelper + terminalActivator .setup(h => h.activateEnvironmentInTerminal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve()) + .returns(() => Promise.resolve(true)) .verifiable(TypeMoq.Times.once()); terminalManager .setup(t => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object) @@ -143,6 +146,7 @@ suite('Terminal Service', () => { await service.show(); terminalHelper.verifyAll(); + terminalActivator.verifyAll(); terminal.verify(t => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.atLeastOnce()); }); @@ -152,9 +156,9 @@ suite('Terminal Service', () => { terminalHelper .setup(h => h.getTerminalShellPath()).returns(() => '') .verifiable(TypeMoq.Times.once()); - terminalHelper + terminalActivator .setup(h => h.activateEnvironmentInTerminal(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve()) + .returns(() => Promise.resolve(true)) .verifiable(TypeMoq.Times.once()); terminalManager .setup(t => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object) @@ -166,6 +170,7 @@ suite('Terminal Service', () => { await service.sendText(textToSend); terminalHelper.verifyAll(); + terminalActivator.verifyAll(); terminal.verify(t => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.atLeastOnce()); }); diff --git a/src/test/linters/lint.multiroot.test.ts b/src/test/linters/lint.multiroot.test.ts index df56a04324f4..ae12dc01c4db 100644 --- a/src/test/linters/lint.multiroot.test.ts +++ b/src/test/linters/lint.multiroot.test.ts @@ -72,7 +72,7 @@ suite('Multiroot Linting', () => { } async function enableDisableSetting(workspaceFolder, configTarget: ConfigurationTarget, setting: string, value: boolean): Promise { const config = ioc.serviceContainer.get(IConfigurationService); - await config.updateSettingAsync(setting, value, Uri.file(workspaceFolder), configTarget); + await config.updateSetting(setting, value, Uri.file(workspaceFolder), configTarget); } test('Enabling Pylint in root and also in Workspace, should return errors', async () => { diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 809b85d6d168..4c7f57c242a7 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -137,12 +137,12 @@ suite('Linting - General Tests', () => { // Don't run these updates in parallel, as they are updating the same file. const target = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; - await configService.updateSettingAsync('linting.enabled', true, rootWorkspaceUri, target); - await configService.updateSettingAsync('linting.lintOnSave', false, rootWorkspaceUri, target); - await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); + await configService.updateSetting('linting.enabled', true, rootWorkspaceUri, target); + await configService.updateSetting('linting.lintOnSave', false, rootWorkspaceUri, target); + await configService.updateSetting('linting.pylintUseMinimalCheckers', false, workspaceUri); linterManager.getAllLinterInfos().forEach(async (x) => { - await configService.updateSettingAsync(makeSettingKey(x.product), false, rootWorkspaceUri, target); + await configService.updateSetting(makeSettingKey(x.product), false, rootWorkspaceUri, target); }); } @@ -154,7 +154,7 @@ suite('Linting - General Tests', () => { const setting = makeSettingKey(product); const output = ioc.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - await configService.updateSettingAsync(setting, enabled, rootWorkspaceUri, + await configService.updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace); file = file ? file : fileToLint; @@ -247,15 +247,15 @@ suite('Linting - General Tests', () => { await testLinterMessages(Product.pep8, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned); }); test('Pydocstyle with config in root', async () => { - await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); + await configService.updateSetting('linting.pylintUseMinimalCheckers', false, workspaceUri); await fs.copy(path.join(pydocstyleConfigPath27, '.pydocstyle'), path.join(workspaceUri.fsPath, '.pydocstyle')); await testLinterMessages(Product.pydocstyle, path.join(pydocstyleConfigPath27, 'file.py'), []); }); test('PyLint minimal checkers', async () => { const file = path.join(pythoFilesPath, 'minCheck.py'); - await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', true, workspaceUri); + await configService.updateSetting('linting.pylintUseMinimalCheckers', true, workspaceUri); await testEnablingDisablingOfLinter(Product.pylint, false, file); - await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); + await configService.updateSetting('linting.pylintUseMinimalCheckers', false, workspaceUri); await testEnablingDisablingOfLinter(Product.pylint, true, file); }); // tslint:disable-next-line:no-function-expression @@ -274,10 +274,10 @@ suite('Linting - General Tests', () => { await closeActiveWindows(); const document = await workspace.openTextDocument(path.join(pythoFilesPath, 'print.py')); await window.showTextDocument(document); - await configService.updateSettingAsync('linting.enabled', true, workspaceUri); - await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); - await configService.updateSettingAsync('linting.pylintEnabled', true, workspaceUri); - await configService.updateSettingAsync('linting.flake8Enabled', true, workspaceUri); + await configService.updateSetting('linting.enabled', true, workspaceUri); + await configService.updateSetting('linting.pylintUseMinimalCheckers', false, workspaceUri); + await configService.updateSetting('linting.pylintEnabled', true, workspaceUri); + await configService.updateSetting('linting.flake8Enabled', true, workspaceUri); const commands = ioc.serviceContainer.get(ICommandManager); const collection = await commands.executeCommand('python.runLinting') as DiagnosticCollection; @@ -303,7 +303,7 @@ suite('Linting - General Tests', () => { test('Three line output counted as one message', async () => { const maxErrors = 5; const target = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; - await configService.updateSettingAsync('linting.maxNumberOfProblems', maxErrors, rootWorkspaceUri, target); + await configService.updateSetting('linting.maxNumberOfProblems', maxErrors, rootWorkspaceUri, target); await testLinterMessageCount(Product.pylint, threeLineLintsPath, maxErrors); }); }); diff --git a/src/test/unittests/display/main.test.ts b/src/test/unittests/display/main.test.ts index 305de972395c..fa5e8ac124ec 100644 --- a/src/test/unittests/display/main.test.ts +++ b/src/test/unittests/display/main.test.ts @@ -297,7 +297,7 @@ suite('Unit Tests - TestResultDisplay', () => { for (const setting of ['unitTest.promptToConfigure', 'unitTest.pyTestEnabled', 'unitTest.unittestEnabled', 'unitTest.nosetestsEnabled']) { - configurationService.setup(c => c.updateSettingAsync(typeMoq.It.isValue(setting), typeMoq.It.isValue(false))) + configurationService.setup(c => c.updateSetting(typeMoq.It.isValue(setting), typeMoq.It.isValue(false))) .returns(() => Promise.resolve()) .verifiable(typeMoq.Times.once()); }