From db038f653b9d0aeba9d3fafded01b95f5e7562dd Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 1 Nov 2023 00:22:42 +0000 Subject: [PATCH 01/23] z --- pythonFiles/deactivate | 16 ++++-- src/client/interpreter/activation/service.ts | 8 +-- .../deactivateService.ts | 54 +++++++++++++++++++ .../envCollectionActivation/service.ts | 23 ++++++-- src/client/terminals/serviceRegistry.ts | 3 ++ src/client/terminals/types.ts | 6 +++ 6 files changed, 100 insertions(+), 10 deletions(-) mode change 100644 => 100755 pythonFiles/deactivate create mode 100644 src/client/terminals/envCollectionActivation/deactivateService.ts diff --git a/pythonFiles/deactivate b/pythonFiles/deactivate old mode 100644 new mode 100755 index 6ede3da311a9..f25192f46ef9 --- a/pythonFiles/deactivate +++ b/pythonFiles/deactivate @@ -1,8 +1,10 @@ # Same as deactivate in "/bin/activate" deactivate () { if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + echo "here" PATH="${_OLD_VIRTUAL_PATH:-}" export PATH + echo $PATH unset _OLD_VIRTUAL_PATH fi if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then @@ -24,10 +26,18 @@ deactivate () { unset -f deactivate fi } +# Read the JSON file +JSON_FILE="/workspaces/vscode-python/pythonFiles/envVars_bash.json" +# Read the JSON file and set the variables +TEMP_PS1=$(cat "$JSON_FILE" | jq -r '.PS1') +TEMP_PATH=$(cat "$JSON_FILE" | jq -r '.PATH') +TEMP_PYTHONHOME=$(cat "$JSON_FILE" | jq -r '.PYTHONHOME') # Initialize the variables required by deactivate function -_OLD_VIRTUAL_PS1="${PS1:-}" -_OLD_VIRTUAL_PATH="$PATH" +_OLD_VIRTUAL_PS1="${TEMP_PS1:-}" +_OLD_VIRTUAL_PATH="$TEMP_PATH" if [ -n "${PYTHONHOME:-}" ] ; then - _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + _OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}" fi +deactivate +bash diff --git a/src/client/interpreter/activation/service.ts b/src/client/interpreter/activation/service.ts index f97545a5823a..d6a3d608007e 100644 --- a/src/client/interpreter/activation/service.ts +++ b/src/client/interpreter/activation/service.ts @@ -273,15 +273,15 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi } return undefined; } - // Run the activate command collect the environment from it. - const activationCommand = fixActivationCommands(activationCommands).join(' && '); - // In order to make sure we know where the environment output is, - // put in a dummy echo we can look for const commandSeparator = [TerminalShellType.powershell, TerminalShellType.powershellCore].includes( shellInfo.shellType, ) ? ';' : '&&'; + // Run the activate command collect the environment from it. + const activationCommand = fixActivationCommands(activationCommands).join(` ${commandSeparator} `); + // In order to make sure we know where the environment output is, + // put in a dummy echo we can look for command = `${activationCommand} ${commandSeparator} echo '${ENVIRONMENT_PREFIX}' ${commandSeparator} python ${args.join( ' ', )}`; diff --git a/src/client/terminals/envCollectionActivation/deactivateService.ts b/src/client/terminals/envCollectionActivation/deactivateService.ts new file mode 100644 index 000000000000..cd803493f4dc --- /dev/null +++ b/src/client/terminals/envCollectionActivation/deactivateService.ts @@ -0,0 +1,54 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { waitForCondition } from '../../../test/common'; +import { ITerminalManager } from '../../common/application/types'; +import { pathExists } from '../../common/platform/fs-paths'; +import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; +import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; +import { cache } from '../../common/utils/decorators'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { virtualEnvTypes } from '../../pythonEnvironments/info'; +import { ITerminalDeactivateService } from '../types'; + +@injectable() +export class TerminalDeactivateService implements ITerminalDeactivateService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + private readonly envVarScript = path.join(_SCRIPTS_DIR, 'printEnvVariablesToFile.py'); + + private readonly printenvVarScript = path.join(_SCRIPTS_DIR, 'printEnvVariables.py'); + + constructor( + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + ) {} + + @cache(-1, true) + public async getTerminalProcessVariables(shell: string): Promise { + const terminal = this.terminalManager.createTerminal({ + name: `Python ${shell} Deactivate`, + shellPath: shell, + hideFromUser: false, + cwd: _SCRIPTS_DIR, + }); + const globalInterpreters = this.interpreterService + .getInterpreters() + .filter((i) => !virtualEnvTypes.includes(i.envType)); + const shellType = identifyShellFromShellPath(shell); + const outputFile = path.join(_SCRIPTS_DIR, `envVars_${shellType}.json`); + const interpreterPath = + globalInterpreters.length > 0 && globalInterpreters[0] ? globalInterpreters[0].path : 'python'; + const checkIfFileHasBeenCreated = () => pathExists(outputFile); + terminal.sendText(`${interpreterPath} ${this.envVarScript} ${outputFile}`); + terminal.sendText(`${interpreterPath} ${this.printenvVarScript}`); + await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); + } + + public getDeactivateScriptLocation(_shell: string): string { + return path.join(_SCRIPTS_DIR); + } +} diff --git a/src/client/terminals/envCollectionActivation/service.ts b/src/client/terminals/envCollectionActivation/service.ts index 46e70d60d922..23889eaa436c 100644 --- a/src/client/terminals/envCollectionActivation/service.ts +++ b/src/client/terminals/envCollectionActivation/service.ts @@ -13,7 +13,12 @@ import { } from 'vscode'; import { pathExists } from 'fs-extra'; import { IExtensionActivationService } from '../../activation/types'; -import { IApplicationShell, IApplicationEnvironment, IWorkspaceService } from '../../common/application/types'; +import { + IApplicationShell, + IApplicationEnvironment, + IWorkspaceService, + ITerminalManager, +} from '../../common/application/types'; import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; import { IPlatformService } from '../../common/platform/types'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; @@ -37,9 +42,10 @@ import { TerminalShellType } from '../../common/terminal/types'; import { OSType } from '../../common/utils/platform'; import { normCase } from '../../common/platform/fs-paths'; import { PythonEnvType } from '../../pythonEnvironments/base/info'; -import { ITerminalEnvVarCollectionService } from '../types'; +import { ITerminalDeactivateService, ITerminalEnvVarCollectionService } from '../types'; import { ShellIntegrationShells } from './shellIntegration'; import { ProgressService } from '../../common/application/progressService'; +import { sleep } from '../../common/utils/async'; @injectable() export class TerminalEnvVarCollectionService implements IExtensionActivationService, ITerminalEnvVarCollectionService { @@ -79,13 +85,21 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ @inject(IEnvironmentActivationService) private environmentActivationService: IEnvironmentActivationService, @inject(IWorkspaceService) private workspaceService: IWorkspaceService, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(ITerminalDeactivateService) private readonly terminalDeactivateService: ITerminalDeactivateService, @inject(IPathUtils) private readonly pathUtils: IPathUtils, + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, ) { this.separator = platform.osType === OSType.Windows ? ';' : ':'; this.progressService = new ProgressService(this.shell); } public async activate(resource: Resource): Promise { + const terminal = this.terminalManager.createTerminal({ + name: `Python Deactivate`, + shellPath: this.applicationEnvironment.shell, + hideFromUser: true, + }); + sleep(3000).then(() => terminal.show()); try { if (!inTerminalEnvVarExperiment(this.experimentService)) { this.context.environmentVariableCollection.clear(); @@ -203,17 +217,19 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ return; } if (key === 'PATH') { + const deactivate = this.terminalDeactivateService.getDeactivateScriptLocation(shell); if (processEnv.PATH && env.PATH?.endsWith(processEnv.PATH)) { // Prefer prepending to PATH instead of replacing it, as we do not want to replace any // changes to PATH users might have made it in their init scripts (~/.bashrc etc.) const prependedPart = env.PATH.slice(0, -processEnv.PATH.length); - value = prependedPart; + value = `${deactivate}${this.separator}${prependedPart}`; traceVerbose(`Prepending environment variable ${key} in collection with ${value}`); envVarCollection.prepend(key, value, prependOptions); } else { if (!value.endsWith(this.separator)) { value = value.concat(this.separator); } + value = `${deactivate}${this.separator}${value}`; traceVerbose(`Prepending environment variable ${key} in collection to ${value}`); envVarCollection.prepend(key, value, prependOptions); } @@ -233,6 +249,7 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ envVarCollection.description = description; await this.trackTerminalPrompt(shell, resource, env); + await this.terminalDeactivateService.getTerminalProcessVariables(shell); } private isPromptSet = new Map(); diff --git a/src/client/terminals/serviceRegistry.ts b/src/client/terminals/serviceRegistry.ts index a9da776d011a..bd94ecd644e5 100644 --- a/src/client/terminals/serviceRegistry.ts +++ b/src/client/terminals/serviceRegistry.ts @@ -13,12 +13,14 @@ import { ICodeExecutionManager, ICodeExecutionService, ITerminalAutoActivation, + ITerminalDeactivateService, ITerminalEnvVarCollectionService, } from './types'; import { TerminalEnvVarCollectionService } from './envCollectionActivation/service'; import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; import { TerminalDeactivateLimitationPrompt } from './envCollectionActivation/deactivatePrompt'; import { TerminalIndicatorPrompt } from './envCollectionActivation/indicatorPrompt'; +import { TerminalDeactivateService } from './envCollectionActivation/deactivateService'; export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton(ICodeExecutionHelper, CodeExecutionHelper); @@ -42,6 +44,7 @@ export function registerTypes(serviceManager: IServiceManager): void { ITerminalEnvVarCollectionService, TerminalEnvVarCollectionService, ); + serviceManager.addSingleton(ITerminalDeactivateService, TerminalDeactivateService); serviceManager.addSingleton( IExtensionSingleActivationService, TerminalIndicatorPrompt, diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index ba30b8f6d47d..5bca7b6c895e 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -41,3 +41,9 @@ export interface ITerminalEnvVarCollectionService { */ isTerminalPromptSetCorrectly(resource?: Resource): boolean; } + +export const ITerminalDeactivateService = Symbol('ITerminalDeactivateService'); +export interface ITerminalDeactivateService { + getTerminalProcessVariables(shell: string): Promise; + getDeactivateScriptLocation(shell: string): string; +} From 150445bfe070d6c6fa0b5296180a87970c27b7b8 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 1 Nov 2023 17:04:16 -0700 Subject: [PATCH 02/23] YAY --- pythonFiles/deactivate.ps1 | 20 ++++++++++++------- .../deactivateService.ts | 16 +++++++++------ .../envCollectionActivation/service.ts | 6 ------ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/pythonFiles/deactivate.ps1 b/pythonFiles/deactivate.ps1 index 65dd80907d90..34d70ed00205 100644 --- a/pythonFiles/deactivate.ps1 +++ b/pythonFiles/deactivate.ps1 @@ -20,12 +20,18 @@ function global:deactivate ([switch]$NonDestructive) { } } -# Initialize the variables required by deactivate function -if (! $env:VIRTUAL_ENV_DISABLE_PROMPT) { - function global:_OLD_VIRTUAL_PROMPT {""} - copy-item function:prompt function:_OLD_VIRTUAL_PROMPT -} -if (Test-Path env:PYTHONHOME) { +# Load JSON file +$jsonConfig = Get-Content -Raw -Path "C:\Users\karraj\OneDrive - Microsoft\Desktop\vscode-python\pythonFiles\envVars_powershell.json" | ConvertFrom-Json + +# Check if PYTHONHOME exists in the JSON file and set it +if ($jsonConfig.PYTHONHOME) { copy-item env:PYTHONHOME env:_OLD_VIRTUAL_PYTHONHOME + $env:PYTHONHOME = $jsonConfig.PYTHONHOME } -copy-item env:PATH env:_OLD_VIRTUAL_PATH + +# Check if PATH exists in the JSON file and set it +if ($jsonConfig.PATH) { + copy-item env:PATH env:_OLD_VIRTUAL_PATH + $env:PATH = $jsonConfig.PATH +} + diff --git a/src/client/terminals/envCollectionActivation/deactivateService.ts b/src/client/terminals/envCollectionActivation/deactivateService.ts index cd803493f4dc..adc763c7f0a5 100644 --- a/src/client/terminals/envCollectionActivation/deactivateService.ts +++ b/src/client/terminals/envCollectionActivation/deactivateService.ts @@ -4,12 +4,13 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { waitForCondition } from '../../../test/common'; +import { sleep, waitForCondition } from '../../../test/common'; import { ITerminalManager } from '../../common/application/types'; import { pathExists } from '../../common/platform/fs-paths'; import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; import { cache } from '../../common/utils/decorators'; +import { StopWatch } from '../../common/utils/stopWatch'; import { IInterpreterService } from '../../interpreter/contracts'; import { virtualEnvTypes } from '../../pythonEnvironments/info'; import { ITerminalDeactivateService } from '../types'; @@ -29,23 +30,26 @@ export class TerminalDeactivateService implements ITerminalDeactivateService { @cache(-1, true) public async getTerminalProcessVariables(shell: string): Promise { + const shellType = identifyShellFromShellPath(shell); const terminal = this.terminalManager.createTerminal({ - name: `Python ${shell} Deactivate`, + name: `Python ${shellType} Deactivate`, shellPath: shell, - hideFromUser: false, + hideFromUser: true, cwd: _SCRIPTS_DIR, }); + sleep(3000).then(() => terminal.show()); const globalInterpreters = this.interpreterService .getInterpreters() .filter((i) => !virtualEnvTypes.includes(i.envType)); - const shellType = identifyShellFromShellPath(shell); const outputFile = path.join(_SCRIPTS_DIR, `envVars_${shellType}.json`); const interpreterPath = globalInterpreters.length > 0 && globalInterpreters[0] ? globalInterpreters[0].path : 'python'; const checkIfFileHasBeenCreated = () => pathExists(outputFile); - terminal.sendText(`${interpreterPath} ${this.envVarScript} ${outputFile}`); - terminal.sendText(`${interpreterPath} ${this.printenvVarScript}`); + terminal.sendText(`${interpreterPath} "${this.envVarScript}" "${outputFile}"`); + terminal.sendText(`${interpreterPath} "${this.printenvVarScript}"`); + const s = new StopWatch(); await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); + console.log('great', s.elapsedTime); } public getDeactivateScriptLocation(_shell: string): string { diff --git a/src/client/terminals/envCollectionActivation/service.ts b/src/client/terminals/envCollectionActivation/service.ts index 23889eaa436c..dc76ba649a7c 100644 --- a/src/client/terminals/envCollectionActivation/service.ts +++ b/src/client/terminals/envCollectionActivation/service.ts @@ -94,12 +94,6 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ } public async activate(resource: Resource): Promise { - const terminal = this.terminalManager.createTerminal({ - name: `Python Deactivate`, - shellPath: this.applicationEnvironment.shell, - hideFromUser: true, - }); - sleep(3000).then(() => terminal.show()); try { if (!inTerminalEnvVarExperiment(this.experimentService)) { this.context.environmentVariableCollection.clear(); From 7408e4217350782e130f293bf117429bf036ba90 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 2 Nov 2023 18:03:26 +0000 Subject: [PATCH 03/23] Changes --- pythonFiles/deactivate | 2 -- pythonFiles/deactivate.fish | 11 +++-------- 2 files changed, 3 insertions(+), 10 deletions(-) mode change 100644 => 100755 pythonFiles/deactivate.fish diff --git a/pythonFiles/deactivate b/pythonFiles/deactivate index f25192f46ef9..1bd1f2c5ed29 100755 --- a/pythonFiles/deactivate +++ b/pythonFiles/deactivate @@ -1,10 +1,8 @@ # Same as deactivate in "/bin/activate" deactivate () { if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then - echo "here" PATH="${_OLD_VIRTUAL_PATH:-}" export PATH - echo $PATH unset _OLD_VIRTUAL_PATH fi if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then diff --git a/pythonFiles/deactivate.fish b/pythonFiles/deactivate.fish old mode 100644 new mode 100755 index c652a8c1e3d7..d5874e582319 --- a/pythonFiles/deactivate.fish +++ b/pythonFiles/deactivate.fish @@ -1,3 +1,5 @@ +#!/usr/bin/fish + # Same as deactivate in "/bin/activate.fish" function deactivate -d "Exit virtual environment and return to normal shell environment" # reset old environment variables @@ -26,11 +28,4 @@ function deactivate -d "Exit virtual environment and return to normal shell env end end -# Initialize the variables required by deactivate function -set -gx _OLD_VIRTUAL_PATH $PATH -if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" - functions -c fish_prompt vscode_python_old_fish_prompt -end -if set -q PYTHONHOME - set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME -end +set TEMP_PATH 'zzz' From 8e20ff9ad7dac8f195da37fdc3b8636d6f742bcc Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 2 Nov 2023 18:10:15 +0000 Subject: [PATCH 04/23] lint --- .../envCollectionActivation/service.ts | 9 +- ...rminalEnvVarCollectionService.unit.test.ts | 684 ------------------ 2 files changed, 1 insertion(+), 692 deletions(-) delete mode 100644 src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts diff --git a/src/client/terminals/envCollectionActivation/service.ts b/src/client/terminals/envCollectionActivation/service.ts index dc76ba649a7c..5831bc560fba 100644 --- a/src/client/terminals/envCollectionActivation/service.ts +++ b/src/client/terminals/envCollectionActivation/service.ts @@ -13,12 +13,7 @@ import { } from 'vscode'; import { pathExists } from 'fs-extra'; import { IExtensionActivationService } from '../../activation/types'; -import { - IApplicationShell, - IApplicationEnvironment, - IWorkspaceService, - ITerminalManager, -} from '../../common/application/types'; +import { IApplicationShell, IApplicationEnvironment, IWorkspaceService } from '../../common/application/types'; import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; import { IPlatformService } from '../../common/platform/types'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; @@ -45,7 +40,6 @@ import { PythonEnvType } from '../../pythonEnvironments/base/info'; import { ITerminalDeactivateService, ITerminalEnvVarCollectionService } from '../types'; import { ShellIntegrationShells } from './shellIntegration'; import { ProgressService } from '../../common/application/progressService'; -import { sleep } from '../../common/utils/async'; @injectable() export class TerminalEnvVarCollectionService implements IExtensionActivationService, ITerminalEnvVarCollectionService { @@ -87,7 +81,6 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ @inject(IConfigurationService) private readonly configurationService: IConfigurationService, @inject(ITerminalDeactivateService) private readonly terminalDeactivateService: ITerminalDeactivateService, @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, ) { this.separator = platform.osType === OSType.Windows ? ';' : ':'; this.progressService = new ProgressService(this.shell); diff --git a/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts b/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts deleted file mode 100644 index 88b9c978854c..000000000000 --- a/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts +++ /dev/null @@ -1,684 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as sinon from 'sinon'; -import { assert, expect } from 'chai'; -import { mock, instance, when, anything, verify, reset } from 'ts-mockito'; -import { - EnvironmentVariableCollection, - EnvironmentVariableMutatorOptions, - GlobalEnvironmentVariableCollection, - ProgressLocation, - Uri, - WorkspaceConfiguration, - WorkspaceFolder, -} from 'vscode'; -import { - IApplicationShell, - IApplicationEnvironment, - IWorkspaceService, -} from '../../../client/common/application/types'; -import { TerminalEnvVarActivation } from '../../../client/common/experiments/groups'; -import { IPlatformService } from '../../../client/common/platform/types'; -import { - IExtensionContext, - IExperimentService, - Resource, - IConfigurationService, - IPythonSettings, -} from '../../../client/common/types'; -import { Interpreters } from '../../../client/common/utils/localize'; -import { OSType, getOSType } from '../../../client/common/utils/platform'; -import { defaultShells } from '../../../client/interpreter/activation/service'; -import { TerminalEnvVarCollectionService } from '../../../client/terminals/envCollectionActivation/service'; -import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { IInterpreterService } from '../../../client/interpreter/contracts'; -import { PathUtils } from '../../../client/common/platform/pathUtils'; -import { PythonEnvType } from '../../../client/pythonEnvironments/base/info'; -import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; - -suite('Terminal Environment Variable Collection Service', () => { - let platform: IPlatformService; - let interpreterService: IInterpreterService; - let context: IExtensionContext; - let shell: IApplicationShell; - let experimentService: IExperimentService; - let collection: EnvironmentVariableCollection; - let globalCollection: GlobalEnvironmentVariableCollection; - let applicationEnvironment: IApplicationEnvironment; - let environmentActivationService: IEnvironmentActivationService; - let workspaceService: IWorkspaceService; - let workspaceConfig: WorkspaceConfiguration; - let terminalEnvVarCollectionService: TerminalEnvVarCollectionService; - const progressOptions = { - location: ProgressLocation.Window, - title: Interpreters.activatingTerminals, - }; - let configService: IConfigurationService; - const displayPath = 'display/path'; - const customShell = 'powershell'; - const defaultShell = defaultShells[getOSType()]; - - setup(() => { - workspaceService = mock(); - workspaceConfig = mock(); - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(undefined); - when(workspaceService.workspaceFolders).thenReturn(undefined); - when(workspaceService.getConfiguration('terminal')).thenReturn(instance(workspaceConfig)); - when(workspaceConfig.get('integrated.shellIntegration.enabled')).thenReturn(true); - platform = mock(); - when(platform.osType).thenReturn(getOSType()); - interpreterService = mock(); - context = mock(); - shell = mock(); - globalCollection = mock(); - collection = mock(); - when(context.environmentVariableCollection).thenReturn(instance(globalCollection)); - when(globalCollection.getScoped(anything())).thenReturn(instance(collection)); - experimentService = mock(); - when(experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)).thenReturn(true); - applicationEnvironment = mock(); - when(applicationEnvironment.shell).thenReturn(customShell); - when(shell.withProgress(anything(), anything())) - .thenCall((options, _) => { - expect(options).to.deep.equal(progressOptions); - }) - .thenResolve(); - environmentActivationService = mock(); - when(environmentActivationService.getProcessEnvironmentVariables(anything(), anything())).thenResolve( - process.env, - ); - configService = mock(); - when(configService.getSettings(anything())).thenReturn(({ - terminal: { activateEnvironment: true }, - pythonPath: displayPath, - } as unknown) as IPythonSettings); - when(collection.clear()).thenResolve(); - terminalEnvVarCollectionService = new TerminalEnvVarCollectionService( - instance(platform), - instance(interpreterService), - instance(context), - instance(shell), - instance(experimentService), - instance(applicationEnvironment), - [], - instance(environmentActivationService), - instance(workspaceService), - instance(configService), - new PathUtils(getOSType() === OSType.Windows), - ); - }); - - teardown(() => { - sinon.restore(); - }); - - test('Apply activated variables to the collection on activation', async () => { - const applyCollectionStub = sinon.stub(terminalEnvVarCollectionService, '_applyCollection'); - applyCollectionStub.resolves(); - when(interpreterService.onDidChangeInterpreter(anything(), anything(), anything())).thenReturn(); - when(applicationEnvironment.onDidChangeShell(anything(), anything(), anything())).thenReturn(); - await terminalEnvVarCollectionService.activate(undefined); - assert(applyCollectionStub.calledOnce, 'Collection not applied on activation'); - }); - - test('When not in experiment, do not apply activated variables to the collection and clear it instead', async () => { - reset(experimentService); - when(context.environmentVariableCollection).thenReturn(instance(collection)); - when(experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)).thenReturn(false); - const applyCollectionStub = sinon.stub(terminalEnvVarCollectionService, '_applyCollection'); - applyCollectionStub.resolves(); - when(interpreterService.onDidChangeInterpreter(anything(), anything(), anything())).thenReturn(); - when(applicationEnvironment.onDidChangeShell(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService.activate(undefined); - - verify(interpreterService.onDidChangeInterpreter(anything(), anything(), anything())).once(); - verify(applicationEnvironment.onDidChangeShell(anything(), anything(), anything())).never(); - assert(applyCollectionStub.notCalled, 'Collection should not be applied on activation'); - - verify(collection.clear()).atLeast(1); - }); - - test('When interpreter changes, apply new activated variables to the collection', async () => { - const applyCollectionStub = sinon.stub(terminalEnvVarCollectionService, '_applyCollection'); - applyCollectionStub.resolves(); - const resource = Uri.file('x'); - let callback: (resource: Resource) => Promise; - when(interpreterService.onDidChangeInterpreter(anything(), anything(), anything())).thenCall((cb) => { - callback = cb; - }); - when(applicationEnvironment.onDidChangeShell(anything(), anything(), anything())).thenReturn(); - await terminalEnvVarCollectionService.activate(undefined); - - await callback!(resource); - assert(applyCollectionStub.calledWithExactly(resource)); - }); - - test('When selected shell changes, apply new activated variables to the collection', async () => { - const applyCollectionStub = sinon.stub(terminalEnvVarCollectionService, '_applyCollection'); - applyCollectionStub.resolves(); - let callback: (shell: string) => Promise; - when(applicationEnvironment.onDidChangeShell(anything(), anything(), anything())).thenCall((cb) => { - callback = cb; - }); - when(interpreterService.onDidChangeInterpreter(anything(), anything(), anything())).thenReturn(); - await terminalEnvVarCollectionService.activate(undefined); - - await callback!(customShell); - assert(applyCollectionStub.calledWithExactly(undefined, customShell)); - }); - - test('If activated variables are returned for custom shell, apply it correctly to the collection', async () => { - const envVars: NodeJS.ProcessEnv = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - customShell, - ), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.clear()).once(); - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).once(); - }); - - // eslint-disable-next-line consistent-return - test('If activated variables contain PS1, prefix it using shell integration', async function () { - if (getOSType() === OSType.Windows) { - return this.skip(); - } - const envVars: NodeJS.ProcessEnv = { - CONDA_PREFIX: 'prefix/to/conda', - ...process.env, - PS1: '(envName) extra prompt', // Should not use this - }; - when( - environmentActivationService.getActivatedEnvironmentVariables(anything(), undefined, undefined, 'bash'), - ).thenResolve(envVars); - - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - envName: 'envName', - } as unknown) as PythonEnvironment); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - let opts: EnvironmentVariableMutatorOptions | undefined; - when(collection.prepend('PS1', '(envName) ', anything())).thenCall((_, _v, o) => { - opts = o; - }); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.clear()).once(); - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).once(); - assert.deepEqual(opts, { applyAtProcessCreation: false, applyAtShellIntegration: true }); - }); - - test('Respect VIRTUAL_ENV_DISABLE_PROMPT when setting PS1 for venv', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { - VIRTUAL_BIN: 'prefix/to/conda', - ...process.env, - VIRTUAL_ENV_DISABLE_PROMPT: '1', - }; - when( - environmentActivationService.getActivatedEnvironmentVariables(anything(), undefined, undefined, 'bash'), - ).thenResolve(envVars); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - envName: 'envName', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - when(collection.prepend('PS1', anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(undefined, 'bash'); - - verify(collection.prepend('PS1', anything(), anything())).never(); - }); - - test('Otherwise set PS1 for venv even if PS1 is not returned', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { - VIRTUAL_BIN: 'prefix/to/conda', - ...process.env, - }; - when( - environmentActivationService.getActivatedEnvironmentVariables(anything(), undefined, undefined, 'bash'), - ).thenResolve(envVars); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - envName: 'envName', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - when(collection.prepend('PS1', '(envName) ', anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(undefined, 'bash'); - - verify(collection.prepend('PS1', '(envName) ', anything())).once(); - }); - - test('Respect CONDA_PROMPT_MODIFIER when setting PS1 for conda', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { - CONDA_PREFIX: 'prefix/to/conda', - ...process.env, - CONDA_PROMPT_MODIFIER: '(envName)', - }; - when( - environmentActivationService.getActivatedEnvironmentVariables(anything(), undefined, undefined, 'bash'), - ).thenResolve(envVars); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Conda, - envName: 'envName', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - let opts: EnvironmentVariableMutatorOptions | undefined; - when(collection.prepend('PS1', '(envName) ', anything())).thenCall((_, _v, o) => { - opts = o; - }); - - await terminalEnvVarCollectionService._applyCollection(undefined, 'bash'); - - verify(collection.clear()).once(); - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).once(); - assert.deepEqual(opts, { applyAtProcessCreation: false, applyAtShellIntegration: true }); - }); - - test('Prepend only "prepend portion of PATH" where applicable', async () => { - const processEnv = { PATH: 'hello/1/2/3' }; - reset(environmentActivationService); - when(environmentActivationService.getProcessEnvironmentVariables(anything(), anything())).thenResolve( - processEnv, - ); - const prependedPart = 'path/to/activate/dir:'; - const envVars: NodeJS.ProcessEnv = { PATH: `${prependedPart}${processEnv.PATH}` }; - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - customShell, - ), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - let opts: EnvironmentVariableMutatorOptions | undefined; - when(collection.prepend('PATH', anything(), anything())).thenCall((_, _v, o) => { - opts = o; - }); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.clear()).once(); - verify(collection.prepend('PATH', prependedPart, anything())).once(); - verify(collection.replace('PATH', anything(), anything())).never(); - assert.deepEqual(opts, { applyAtProcessCreation: false, applyAtShellIntegration: true }); - }); - - test('Prepend full PATH with separator otherwise', async () => { - const processEnv = { PATH: 'hello/1/2/3' }; - reset(environmentActivationService); - when(environmentActivationService.getProcessEnvironmentVariables(anything(), anything())).thenResolve( - processEnv, - ); - const separator = getOSType() === OSType.Windows ? ';' : ':'; - const finalPath = 'hello/3/2/1'; - const envVars: NodeJS.ProcessEnv = { PATH: finalPath }; - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - customShell, - ), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - let opts: EnvironmentVariableMutatorOptions | undefined; - when(collection.prepend('PATH', anything(), anything())).thenCall((_, _v, o) => { - opts = o; - }); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.clear()).once(); - verify(collection.prepend('PATH', `${finalPath}${separator}`, anything())).once(); - verify(collection.replace('PATH', anything(), anything())).never(); - assert.deepEqual(opts, { applyAtProcessCreation: false, applyAtShellIntegration: true }); - }); - - test('Verify envs are not applied if env activation is disabled', async () => { - const envVars: NodeJS.ProcessEnv = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - customShell, - ), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - reset(configService); - when(configService.getSettings(anything())).thenReturn(({ - terminal: { activateEnvironment: false }, - pythonPath: displayPath, - } as unknown) as IPythonSettings); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.clear()).once(); - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).never(); - }); - - test('Verify correct options are used when applying envs and setting description', async () => { - const envVars: NodeJS.ProcessEnv = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, customShell), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenCall( - (_e, _v, options: EnvironmentVariableMutatorOptions) => { - assert.deepEqual(options, { applyAtShellIntegration: true, applyAtProcessCreation: true }); - return Promise.resolve(); - }, - ); - - await terminalEnvVarCollectionService._applyCollection(resource, customShell); - - verify(collection.clear()).once(); - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).once(); - }); - - test('Correct track that prompt was set for non-Windows bash where PS1 is set', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { VIRTUAL_ENV: 'prefix/to/venv', PS1: '(.venv)', ...process.env }; - const ps1Shell = 'bash'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(true); - }); - - test('Correct track that prompt was set for PS1 if shell integration is disabled', async () => { - reset(workspaceConfig); - when(workspaceConfig.get('integrated.shellIntegration.enabled')).thenReturn(false); - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { VIRTUAL_ENV: 'prefix/to/venv', PS1: '(.venv)', ...process.env }; - const ps1Shell = 'bash'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(false); - }); - - test('Correct track that prompt was set for non-Windows where PS1 is not set but should be set', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - const ps1Shell = 'zsh'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Conda, - envName: 'envName', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(true); - }); - - test('Correct track that prompt was not set for non-Windows where PS1 is not set but env name is base', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { - CONDA_PREFIX: 'prefix/to/conda', - ...process.env, - CONDA_PROMPT_MODIFIER: '(base)', - }; - const ps1Shell = 'zsh'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Conda, - envName: 'base', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(false); - }); - - test('Correct track that prompt was not set for non-Windows fish where PS1 is not set', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - const ps1Shell = 'fish'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Conda, - envName: 'envName', - envPath: 'prefix/to/conda', - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(false); - }); - - test('Correct track that prompt was set correctly for global interpreters', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const ps1Shell = 'zsh'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: undefined, - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, ps1Shell), - ).thenResolve(undefined); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, ps1Shell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(true); - }); - - test('Correct track that prompt was set for Windows when not using powershell', async () => { - when(platform.osType).thenReturn(OSType.Windows); - const envVars: NodeJS.ProcessEnv = { VIRTUAL_ENV: 'prefix/to/venv', ...process.env }; - const windowsShell = 'cmd'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, windowsShell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, windowsShell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(true); - }); - - test('Correct track that prompt was not set for Windows when using powershell', async () => { - when(platform.osType).thenReturn(OSType.Linux); - const envVars: NodeJS.ProcessEnv = { VIRTUAL_ENV: 'prefix/to/venv', ...process.env }; - const windowsShell = 'powershell'; - const resource = Uri.file('a'); - const workspaceFolder: WorkspaceFolder = { - uri: Uri.file('workspacePath'), - name: 'workspace1', - index: 0, - }; - when(interpreterService.getActiveInterpreter(resource)).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(workspaceService.getWorkspaceFolder(resource)).thenReturn(workspaceFolder); - when( - environmentActivationService.getActivatedEnvironmentVariables(resource, undefined, undefined, windowsShell), - ).thenResolve(envVars); - when(collection.replace(anything(), anything(), anything())).thenReturn(); - - await terminalEnvVarCollectionService._applyCollection(resource, windowsShell); - - const result = terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource); - - expect(result).to.equal(false); - }); - - test('If no activated variables are returned for custom shell, fallback to using default shell', async () => { - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - customShell, - ), - ).thenResolve(undefined); - const envVars = { CONDA_PREFIX: 'prefix/to/conda', ...process.env }; - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - defaultShell?.shell, - ), - ).thenResolve(envVars); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - - await terminalEnvVarCollectionService._applyCollection(undefined, customShell); - - verify(collection.replace('CONDA_PREFIX', 'prefix/to/conda', anything())).once(); - verify(collection.clear()).once(); - }); - - test('If no activated variables are returned for default shell, clear collection', async () => { - when( - environmentActivationService.getActivatedEnvironmentVariables( - anything(), - undefined, - undefined, - defaultShell?.shell, - ), - ).thenResolve(undefined); - - when(collection.replace(anything(), anything(), anything())).thenResolve(); - when(collection.delete(anything())).thenResolve(); - - await terminalEnvVarCollectionService._applyCollection(undefined, defaultShell?.shell); - - verify(collection.clear()).once(); - }); -}); From 5a47445c183daad11dcb2304fba6a53743b5cbef Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 2 Nov 2023 18:12:40 +0000 Subject: [PATCH 05/23] only build vsix --- .github/workflows/pr-check.yml | 494 --------------------------------- 1 file changed, 494 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 4ade4fd2af1e..cd0d8ba44c6a 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -33,497 +33,3 @@ jobs: node_version: ${{ env.NODE_VERSION}} vsix_name: ${{ env.VSIX_NAME }} artifact_name: ${{ env.ARTIFACT_NAME_VSIX }} - - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Lint - uses: ./.github/actions/lint - with: - node_version: ${{ env.NODE_VERSION }} - - check-types: - name: Check Python types - runs-on: ubuntu-latest - steps: - - name: Use Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Checkout - uses: actions/checkout@v4 - - - name: Install base Python requirements - uses: brettcannon/pip-secure-install@v1 - with: - options: '-t ./pythonFiles/lib/python --no-cache-dir --implementation py' - - - name: Install Jedi requirements - uses: brettcannon/pip-secure-install@v1 - with: - requirements-file: './pythonFiles/jedilsp_requirements/requirements.txt' - options: '-t ./pythonFiles/lib/jedilsp --no-cache-dir --implementation py' - - - name: Install other Python requirements - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - python -m pip install --upgrade -r build/test-requirements.txt - - - name: Run Pyright - uses: jakebailey/pyright-action@v1 - with: - version: 1.1.308 - working-directory: 'pythonFiles' - - python-tests: - name: Python Tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: ${{ env.special-working-directory }} - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. - os: [ubuntu-latest, windows-latest] - # Run the tests on the oldest and most recent versions of Python. - python: ['3.8', '3.x', '3.12-dev'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - path: ${{ env.special-working-directory-relative }} - - - name: Use Python ${{ matrix.python }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - - name: Install base Python requirements - uses: brettcannon/pip-secure-install@v1 - with: - requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"' - options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py' - - - name: Install test requirements - run: python -m pip install --upgrade -r build/test-requirements.txt - - - name: Run Python unit tests - run: python pythonFiles/tests/run_all.py - - tests: - name: Tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: ${{ env.special-working-directory }} - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. - os: [ubuntu-latest, windows-latest] - # Run the tests on the oldest and most recent versions of Python. - python: ['3.x'] - test-suite: [ts-unit, venv, single-workspace, debugger, functional] - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - path: ${{ env.special-working-directory-relative }} - - - name: Install Node - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: ${{ env.special-working-directory-relative }}/package-lock.json - - - name: Install dependencies (npm ci) - run: npm ci - - - name: Compile - run: npx gulp prePublishNonBundle - - - name: Use Python ${{ matrix.python }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - - name: Install debugpy - run: | - # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - - - name: Download get-pip.py - run: | - python -m pip install wheel - python -m pip install -r build/build-install-requirements.txt - python ./pythonFiles/download_get_pip.py - shell: bash - - - name: Install base Python requirements - uses: brettcannon/pip-secure-install@v1 - with: - requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"' - options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/python" --no-cache-dir --implementation py' - - - name: Install Jedi requirements - uses: brettcannon/pip-secure-install@v1 - with: - requirements-file: '"${{ env.special-working-directory-relative }}/pythonFiles/jedilsp_requirements/requirements.txt"' - options: '-t "${{ env.special-working-directory-relative }}/pythonFiles/lib/jedilsp" --no-cache-dir --implementation py' - - - name: Install test requirements - run: python -m pip install --upgrade -r build/test-requirements.txt - - - name: Install debugpy wheels (Python ${{ matrix.python }}) - run: | - python -m pip install wheel - python -m pip --disable-pip-version-check install -r build/build-install-requirements.txt - python ./pythonFiles/install_debugpy.py - shell: bash - if: matrix.test-suite == 'debugger' - - - name: Install functional test requirements - run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt - if: matrix.test-suite == 'functional' - - - name: Prepare pipenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install pipenv - python -m pipenv run python ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} pipenvPath - - - name: Prepare poetry for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install poetry - Move-Item -Path ".\build\ci\pyproject.toml" -Destination . - poetry env use python - - - name: Prepare virtualenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install virtualenv - python -m virtualenv .virtualenv/ - if ('${{ matrix.os }}' -match 'windows-latest') { - & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath - } else { - & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath - } - - - name: Prepare venv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' && startsWith(matrix.python, 3.) - run: | - python -m venv .venv - if ('${{ matrix.os }}' -match 'windows-latest') { - & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath - } else { - & ".venv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath - } - - - name: Prepare conda for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - # 1. For `terminalActivation.testvirtualenvs.test.ts` - if ('${{ matrix.os }}' -match 'windows-latest') { - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda - } else{ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda - } - & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaExecPath $condaExecPath - & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath - & $condaExecPath init --all - - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION - run: | - echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV - echo "CI_DISABLE_AUTO_SELECTION=1" >> $GITHUB_ENV - shell: bash - if: matrix.test-suite != 'ts-unit' - - # Run TypeScript unit tests only for Python 3.X. - - name: Run TypeScript unit tests - run: npm run test:unittests - if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, 3.) - - # The virtual environment based tests use the `testSingleWorkspace` set of tests - # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, - # which is set in the "Prepare environment for venv tests" step. - # We also use a third-party GitHub Action to install xvfb on Linux, - # run tests and then clean up the process once the tests ran. - # See https://github.com/GabrielBB/xvfb-action - - name: Run venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - CI_PYTHON_VERSION: ${{ matrix.python }} - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testSingleWorkspace - working-directory: ${{ env.special-working-directory }} - if: matrix.test-suite == 'venv' && matrix.os == 'ubuntu-latest' - - - name: Run single-workspace tests - env: - CI_PYTHON_VERSION: ${{ matrix.python }} - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testSingleWorkspace - working-directory: ${{ env.special-working-directory }} - if: matrix.test-suite == 'single-workspace' - - - name: Run debugger tests - env: - CI_PYTHON_VERSION: ${{ matrix.python }} - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testDebugger - working-directory: ${{ env.special-working-directory }} - if: matrix.test-suite == 'debugger' - - # Run TypeScript functional tests - - name: Run TypeScript functional tests - run: npm run test:functional - if: matrix.test-suite == 'functional' - - smoke-tests: - name: Smoke tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - needs: [build-vsix] - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] - steps: - # Need the source to have the tests available. - - name: Checkout - uses: actions/checkout@v4 - - - name: Smoke tests - uses: ./.github/actions/smoke-tests - with: - node_version: ${{ env.NODE_VERSION }} - artifact_name: ${{ env.ARTIFACT_NAME_VSIX }} - - ### Coverage run - coverage: - name: Coverage - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - # Only run coverage on linux for PRs - os: [ubuntu-latest] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install Node - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies (npm ci) - run: npm ci - - - name: Compile - run: npx gulp prePublishNonBundle - - - name: Use Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v4 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: 'pip' - cache-dependency-path: | - requirements.txt - pythonFiles/jedilsp_requirements/requirements.txt - build/test-requirements.txt - build/functional-test-requirements.txt - - - name: Install base Python requirements - uses: brettcannon/pip-secure-install@v1 - with: - options: '-t ./pythonFiles/lib/python --implementation py' - - - name: Install Jedi requirements - uses: brettcannon/pip-secure-install@v1 - with: - requirements-file: './pythonFiles/jedilsp_requirements/requirements.txt' - options: '-t ./pythonFiles/lib/jedilsp --implementation py' - - - name: Install debugpy - run: | - # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --implementation py --no-deps --upgrade --pre debugpy - - - name: Install test requirements - run: python -m pip install --upgrade -r build/test-requirements.txt - - - name: Install functional test requirements - run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt - - - name: Prepare pipenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - run: | - python -m pip install pipenv - python -m pipenv run python ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} pipenvPath - - - name: Prepare poetry for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - shell: pwsh - run: | - python -m pip install poetry - Move-Item -Path ".\build\ci\pyproject.toml" -Destination . - poetry env use python - - - name: Prepare virtualenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - run: | - python -m pip install virtualenv - python -m virtualenv .virtualenv/ - if ('${{ matrix.os }}' -match 'windows-latest') { - & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath - } else { - & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath - } - - - name: Prepare venv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - run: | - python -m venv .venv - if ('${{ matrix.os }}' -match 'windows-latest') { - & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath - } else { - & ".venv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath - } - - - name: Prepare conda for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - run: | - # 1. For `terminalActivation.testvirtualenvs.test.ts` - if ('${{ matrix.os }}' -match 'windows-latest') { - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda - } else{ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda - } - & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaExecPath $condaExecPath - & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath - & $condaExecPath init --all - - - name: Run TypeScript unit tests - run: npm run test:unittests:cover - - - name: Run Python unit tests - run: | - python pythonFiles/tests/run_all.py - - # The virtual environment based tests use the `testSingleWorkspace` set of tests - # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, - # which is set in the "Prepare environment for venv tests" step. - # We also use a third-party GitHub Action to install xvfb on Linux, - # run tests and then clean up the process once the tests ran. - # See https://github.com/GabrielBB/xvfb-action - - name: Run venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} - CI_DISABLE_AUTO_SELECTION: 1 - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testSingleWorkspace:cover - - - name: Run single-workspace tests - env: - CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} - CI_DISABLE_AUTO_SELECTION: 1 - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testSingleWorkspace:cover - - # Enable these tests when coverage is setup for multiroot workspace tests - # - name: Run multi-workspace tests - # env: - # CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} - # CI_DISABLE_AUTO_SELECTION: 1 - # uses: GabrielBB/xvfb-action@v1.6 - # with: - # run: npm run testMultiWorkspace:cover - - # Enable these tests when coverage is setup for debugger tests - # - name: Run debugger tests - # env: - # CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} - # CI_DISABLE_AUTO_SELECTION: 1 - # uses: GabrielBB/xvfb-action@v1.6 - # with: - # run: npm run testDebugger:cover - - # Run TypeScript functional tests - - name: Run TypeScript functional tests - env: - CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} - CI_DISABLE_AUTO_SELECTION: 1 - run: npm run test:functional:cover - - - name: Generate coverage reports - run: npm run test:cover:report - - - name: Upload HTML report - uses: actions/upload-artifact@v3 - with: - name: ${{ runner.os }}-coverage-report-html - path: ./coverage - retention-days: 1 From c06ec22445b6a145ae80314137215f1a40f3de77 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Mon, 6 Nov 2023 22:38:24 +0000 Subject: [PATCH 06/23] xz --- .../terminals/envCollectionActivation/deactivateService.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/terminals/envCollectionActivation/deactivateService.ts b/src/client/terminals/envCollectionActivation/deactivateService.ts index adc763c7f0a5..6be2f9ad736c 100644 --- a/src/client/terminals/envCollectionActivation/deactivateService.ts +++ b/src/client/terminals/envCollectionActivation/deactivateService.ts @@ -10,7 +10,6 @@ import { pathExists } from '../../common/platform/fs-paths'; import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; import { cache } from '../../common/utils/decorators'; -import { StopWatch } from '../../common/utils/stopWatch'; import { IInterpreterService } from '../../interpreter/contracts'; import { virtualEnvTypes } from '../../pythonEnvironments/info'; import { ITerminalDeactivateService } from '../types'; @@ -47,9 +46,7 @@ export class TerminalDeactivateService implements ITerminalDeactivateService { const checkIfFileHasBeenCreated = () => pathExists(outputFile); terminal.sendText(`${interpreterPath} "${this.envVarScript}" "${outputFile}"`); terminal.sendText(`${interpreterPath} "${this.printenvVarScript}"`); - const s = new StopWatch(); await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); - console.log('great', s.elapsedTime); } public getDeactivateScriptLocation(_shell: string): string { From cc38abb6daaf5cc6e5621e67f6dc13c79ac653db Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 7 Nov 2023 20:36:29 +0000 Subject: [PATCH 07/23] Remove old way and install new way --- pythonFiles/{ => deactivate/bash}/deactivate | 7 +- pythonFiles/deactivate/fish/deactivate | 44 +++ .../powershell}/deactivate.ps1 | 0 pythonFiles/deactivate/zsh/deactivate | 44 +++ .../deactivatePrompt.ts | 201 ------------- .../deactivateScripts.ts | 108 ------- .../deactivateService.ts | 25 +- .../envCollectionActivation/service.ts | 8 +- src/client/terminals/serviceRegistry.ts | 5 - src/client/terminals/types.ts | 2 +- .../deactivatePrompt.unit.test.ts | 271 ------------------ .../terminals/serviceRegistry.unit.test.ts | 2 - 12 files changed, 117 insertions(+), 600 deletions(-) rename pythonFiles/{ => deactivate/bash}/deactivate (86%) create mode 100755 pythonFiles/deactivate/fish/deactivate rename pythonFiles/{ => deactivate/powershell}/deactivate.ps1 (100%) create mode 100755 pythonFiles/deactivate/zsh/deactivate delete mode 100644 src/client/terminals/envCollectionActivation/deactivatePrompt.ts delete mode 100644 src/client/terminals/envCollectionActivation/deactivateScripts.ts delete mode 100644 src/test/terminals/envCollectionActivation/deactivatePrompt.unit.test.ts diff --git a/pythonFiles/deactivate b/pythonFiles/deactivate/bash/deactivate similarity index 86% rename from pythonFiles/deactivate rename to pythonFiles/deactivate/bash/deactivate index 1bd1f2c5ed29..c0ea72fd2a7b 100755 --- a/pythonFiles/deactivate +++ b/pythonFiles/deactivate/bash/deactivate @@ -24,8 +24,11 @@ deactivate () { unset -f deactivate fi } -# Read the JSON file -JSON_FILE="/workspaces/vscode-python/pythonFiles/envVars_bash.json" + +# Get the directory of the current script +SCRIPT_DIR=$(dirname "$0") +# Construct the path to envVars.json relative to the script directory +JSON_FILE="$SCRIPT_DIR/envVars.json" # Read the JSON file and set the variables TEMP_PS1=$(cat "$JSON_FILE" | jq -r '.PS1') diff --git a/pythonFiles/deactivate/fish/deactivate b/pythonFiles/deactivate/fish/deactivate new file mode 100755 index 000000000000..9bfb62cb1240 --- /dev/null +++ b/pythonFiles/deactivate/fish/deactivate @@ -0,0 +1,44 @@ +# Same as deactivate in "/bin/activate" +deactivate () { + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + unset -f deactivate + fi +} + +# Get the directory of the current script +SCRIPT_DIR=$(dirname "$0") +# Construct the path to envVars.json relative to the script directory +JSON_FILE="$SCRIPT_DIR/envVars.json" + +# Read the JSON file and set the variables +TEMP_PS1=$(cat "$JSON_FILE" | jq -r '.PS1') +TEMP_PATH=$(cat "$JSON_FILE" | jq -r '.PATH') +TEMP_PYTHONHOME=$(cat "$JSON_FILE" | jq -r '.PYTHONHOME') +# Initialize the variables required by deactivate function +_OLD_VIRTUAL_PS1="${TEMP_PS1:-}" +_OLD_VIRTUAL_PATH="$TEMP_PATH" +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}" +fi +deactivate +fish diff --git a/pythonFiles/deactivate.ps1 b/pythonFiles/deactivate/powershell/deactivate.ps1 similarity index 100% rename from pythonFiles/deactivate.ps1 rename to pythonFiles/deactivate/powershell/deactivate.ps1 diff --git a/pythonFiles/deactivate/zsh/deactivate b/pythonFiles/deactivate/zsh/deactivate new file mode 100755 index 000000000000..5ceef4777a99 --- /dev/null +++ b/pythonFiles/deactivate/zsh/deactivate @@ -0,0 +1,44 @@ +# Same as deactivate in "/bin/activate" +deactivate () { + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + unset -f deactivate + fi +} + +# Get the directory of the current script +SCRIPT_DIR=$(dirname "$0") +# Construct the path to envVars.json relative to the script directory +JSON_FILE="$SCRIPT_DIR/envVars.json" + +# Read the JSON file and set the variables +TEMP_PS1=$(cat "$JSON_FILE" | jq -r '.PS1') +TEMP_PATH=$(cat "$JSON_FILE" | jq -r '.PATH') +TEMP_PYTHONHOME=$(cat "$JSON_FILE" | jq -r '.PYTHONHOME') +# Initialize the variables required by deactivate function +_OLD_VIRTUAL_PS1="${TEMP_PS1:-}" +_OLD_VIRTUAL_PATH="$TEMP_PATH" +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${TEMP_PYTHONHOME:-}" +fi +deactivate +zsh diff --git a/src/client/terminals/envCollectionActivation/deactivatePrompt.ts b/src/client/terminals/envCollectionActivation/deactivatePrompt.ts deleted file mode 100644 index a9fd804291a5..000000000000 --- a/src/client/terminals/envCollectionActivation/deactivatePrompt.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { - Position, - Uri, - WorkspaceEdit, - Range, - TextEditorRevealType, - ProgressLocation, - Terminal, - Selection, -} from 'vscode'; -import { - IApplicationEnvironment, - IApplicationShell, - IDocumentManager, - ITerminalManager, -} from '../../common/application/types'; -import { IDisposableRegistry, IExperimentService, IPersistentStateFactory } from '../../common/types'; -import { Common, Interpreters } from '../../common/utils/localize'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { PythonEnvType } from '../../pythonEnvironments/base/info'; -import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; -import { TerminalShellType } from '../../common/terminal/types'; -import { traceError } from '../../logging'; -import { shellExec } from '../../common/process/rawProcessApis'; -import { sleep } from '../../common/utils/async'; -import { getDeactivateShellInfo } from './deactivateScripts'; -import { isTestExecution } from '../../common/constants'; -import { ProgressService } from '../../common/application/progressService'; -import { copyFile, createFile, pathExists } from '../../common/platform/fs-paths'; -import { getOSType, OSType } from '../../common/utils/platform'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; - -export const terminalDeactivationPromptKey = 'TERMINAL_DEACTIVATION_PROMPT_KEY'; -@injectable() -export class TerminalDeactivateLimitationPrompt implements IExtensionSingleActivationService { - public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; - - private terminalProcessId: number | undefined; - - private readonly progressService: ProgressService; - - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, - @inject(IExperimentService) private readonly experimentService: IExperimentService, - ) { - this.progressService = new ProgressService(this.appShell); - } - - public async activate(): Promise { - if (!inTerminalEnvVarExperiment(this.experimentService)) { - return; - } - if (!isTestExecution()) { - // Avoid showing prompt until startup completes. - await sleep(6000); - } - this.disposableRegistry.push( - this.appShell.onDidWriteTerminalData(async (e) => { - if (!e.data.includes('deactivate')) { - return; - } - let shellType = identifyShellFromShellPath(this.appEnvironment.shell); - if (shellType === TerminalShellType.commandPrompt) { - return; - } - if (getOSType() === OSType.OSX && shellType === TerminalShellType.bash) { - // On macOS, sometimes bash is overriden by OS to actually launch zsh, so we need to execute inside - // the shell to get the correct shell type. - const shell = await shellExec('echo $SHELL', { shell: this.appEnvironment.shell }).then((output) => - output.stdout.trim(), - ); - shellType = identifyShellFromShellPath(shell); - } - const { terminal } = e; - const cwd = - 'cwd' in terminal.creationOptions && terminal.creationOptions.cwd - ? terminal.creationOptions.cwd - : undefined; - const resource = typeof cwd === 'string' ? Uri.file(cwd) : cwd; - const interpreter = await this.interpreterService.getActiveInterpreter(resource); - if (interpreter?.type !== PythonEnvType.Virtual) { - return; - } - await this._notifyUsers(shellType, terminal).catch((ex) => traceError('Deactivate prompt failed', ex)); - }), - ); - } - - public async _notifyUsers(shellType: TerminalShellType, terminal: Terminal): Promise { - const notificationPromptEnabled = this.persistentStateFactory.createGlobalPersistentState( - `${terminalDeactivationPromptKey}-${shellType}`, - true, - ); - if (!notificationPromptEnabled.value) { - const processId = await terminal.processId; - if (processId && this.terminalProcessId === processId) { - // Existing terminal needs to be restarted for changes to take effect. - await this.forceRestartShell(terminal); - } - return; - } - const scriptInfo = getDeactivateShellInfo(shellType); - if (!scriptInfo) { - // Shell integration is not supported for these shells, in which case this workaround won't work. - return; - } - const telemetrySelections: ['Edit script', "Don't show again"] = ['Edit script', "Don't show again"]; - const { initScript, source, destination } = scriptInfo; - const prompts = [Common.editSomething.format(initScript.displayName), Common.doNotShowAgain]; - const selection = await this.appShell.showWarningMessage( - Interpreters.terminalDeactivatePrompt.format(initScript.displayName), - ...prompts, - ); - let index = selection ? prompts.indexOf(selection) : 0; - if (selection === prompts[0]) { - index = 0; - } - sendTelemetryEvent(EventName.TERMINAL_DEACTIVATE_PROMPT, undefined, { - selection: selection ? telemetrySelections[index] : undefined, - }); - if (!selection) { - return; - } - if (selection === prompts[0]) { - this.progressService.showProgress({ - location: ProgressLocation.Window, - title: Interpreters.terminalDeactivateProgress.format(initScript.displayName), - }); - await copyFile(source, destination); - await this.openScriptWithEdits(initScript.command, initScript.contents); - await notificationPromptEnabled.updateValue(false); - this.progressService.hideProgress(); - this.terminalProcessId = await terminal.processId; - } - if (selection === prompts[1]) { - await notificationPromptEnabled.updateValue(false); - } - } - - private async openScriptWithEdits(command: string, content: string) { - const document = await this.openScript(command); - const hookMarker = 'VSCode venv deactivate hook'; - content = ` -# >>> ${hookMarker} >>> -${content} -# <<< ${hookMarker} <<<`; - // If script already has the hook, don't add it again. - const editor = await this.documentManager.showTextDocument(document); - if (document.getText().includes(hookMarker)) { - editor.revealRange( - new Range(new Position(document.lineCount - 3, 0), new Position(document.lineCount, 0)), - TextEditorRevealType.AtTop, - ); - return; - } - const editorEdit = new WorkspaceEdit(); - editorEdit.insert(document.uri, new Position(document.lineCount, 0), content); - await this.documentManager.applyEdit(editorEdit); - // Reveal the edits. - editor.selection = new Selection(new Position(document.lineCount - 3, 0), new Position(document.lineCount, 0)); - editor.revealRange( - new Range(new Position(document.lineCount - 3, 0), new Position(document.lineCount, 0)), - TextEditorRevealType.AtTop, - ); - } - - private async openScript(command: string) { - const initScriptPath = await this.getPathToScript(command); - if (!(await pathExists(initScriptPath))) { - await createFile(initScriptPath); - } - const document = await this.documentManager.openTextDocument(initScriptPath); - return document; - } - - private async getPathToScript(command: string) { - return shellExec(command, { shell: this.appEnvironment.shell }).then((output) => output.stdout.trim()); - } - - public async forceRestartShell(terminal: Terminal): Promise { - terminal.dispose(); - terminal = this.terminalManager.createTerminal({ - message: Interpreters.restartingTerminal, - }); - terminal.show(true); - terminal.sendText('deactivate'); - } -} diff --git a/src/client/terminals/envCollectionActivation/deactivateScripts.ts b/src/client/terminals/envCollectionActivation/deactivateScripts.ts deleted file mode 100644 index 34917e44bbdf..000000000000 --- a/src/client/terminals/envCollectionActivation/deactivateScripts.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable no-case-declarations */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; -import { TerminalShellType } from '../../common/terminal/types'; - -type DeactivateShellInfo = { - /** - * Full path to source deactivate script to copy. - */ - source: string; - /** - * Full path to destination to copy deactivate script to. - */ - destination: string; - initScript: { - /** - * Display name of init script for the shell. - */ - displayName: string; - /** - * Command to run in shell to output the full path to init script. - */ - command: string; - /** - * Contents to add to init script. - */ - contents: string; - }; -}; - -// eslint-disable-next-line global-require -const untildify: (value: string) => string = require('untildify'); - -export function getDeactivateShellInfo(shellType: TerminalShellType): DeactivateShellInfo | undefined { - switch (shellType) { - case TerminalShellType.bash: - return buildInfo( - 'deactivate', - { - displayName: '~/.bashrc', - path: '~/.bashrc', - }, - `source {0}`, - ); - case TerminalShellType.powershellCore: - case TerminalShellType.powershell: - return buildInfo( - 'deactivate.ps1', - { - displayName: 'Powershell Profile', - path: '$Profile', - }, - `& "{0}"`, - ); - case TerminalShellType.zsh: - return buildInfo( - 'deactivate', - { - displayName: '~/.zshrc', - path: '~/.zshrc', - }, - `source {0}`, - ); - case TerminalShellType.fish: - return buildInfo( - 'deactivate.fish', - { - displayName: 'config.fish', - path: '$__fish_config_dir/config.fish', - }, - `source {0}`, - ); - case TerminalShellType.cshell: - return buildInfo( - 'deactivate.csh', - { - displayName: '~/.cshrc', - path: '~/.cshrc', - }, - `source {0}`, - ); - default: - return undefined; - } -} - -function buildInfo( - deactivate: string, - initScript: { - path: string; - displayName: string; - }, - scriptCommandFormat: string, -) { - const scriptPath = path.join('~', '.vscode-python', deactivate); - return { - source: path.join(_SCRIPTS_DIR, deactivate), - destination: untildify(scriptPath), - initScript: { - displayName: initScript.displayName, - command: `echo ${initScript.path}`, - contents: scriptCommandFormat.format(scriptPath), - }, - }; -} diff --git a/src/client/terminals/envCollectionActivation/deactivateService.ts b/src/client/terminals/envCollectionActivation/deactivateService.ts index 6be2f9ad736c..62297db4fac3 100644 --- a/src/client/terminals/envCollectionActivation/deactivateService.ts +++ b/src/client/terminals/envCollectionActivation/deactivateService.ts @@ -4,15 +4,18 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { sleep, waitForCondition } from '../../../test/common'; +import { waitForCondition } from '../../../test/common'; import { ITerminalManager } from '../../common/application/types'; import { pathExists } from '../../common/platform/fs-paths'; import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; import { cache } from '../../common/utils/decorators'; +import { StopWatch } from '../../common/utils/stopWatch'; import { IInterpreterService } from '../../interpreter/contracts'; +import { traceVerbose } from '../../logging'; import { virtualEnvTypes } from '../../pythonEnvironments/info'; import { ITerminalDeactivateService } from '../types'; +import { ShellIntegrationShells } from './shellIntegration'; @injectable() export class TerminalDeactivateService implements ITerminalDeactivateService { @@ -20,8 +23,6 @@ export class TerminalDeactivateService implements ITerminalDeactivateService { private readonly envVarScript = path.join(_SCRIPTS_DIR, 'printEnvVariablesToFile.py'); - private readonly printenvVarScript = path.join(_SCRIPTS_DIR, 'printEnvVariables.py'); - constructor( @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @@ -29,6 +30,10 @@ export class TerminalDeactivateService implements ITerminalDeactivateService { @cache(-1, true) public async getTerminalProcessVariables(shell: string): Promise { + const location = this.getDeactivateScriptLocation(shell); + if (!location) { + return; + } const shellType = identifyShellFromShellPath(shell); const terminal = this.terminalManager.createTerminal({ name: `Python ${shellType} Deactivate`, @@ -36,20 +41,24 @@ export class TerminalDeactivateService implements ITerminalDeactivateService { hideFromUser: true, cwd: _SCRIPTS_DIR, }); - sleep(3000).then(() => terminal.show()); const globalInterpreters = this.interpreterService .getInterpreters() .filter((i) => !virtualEnvTypes.includes(i.envType)); - const outputFile = path.join(_SCRIPTS_DIR, `envVars_${shellType}.json`); + const outputFile = path.join(location, `envVars.json`); const interpreterPath = globalInterpreters.length > 0 && globalInterpreters[0] ? globalInterpreters[0].path : 'python'; const checkIfFileHasBeenCreated = () => pathExists(outputFile); + const stopWatch = new StopWatch(); terminal.sendText(`${interpreterPath} "${this.envVarScript}" "${outputFile}"`); - terminal.sendText(`${interpreterPath} "${this.printenvVarScript}"`); await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); + traceVerbose(`Time taken to get env vars using terminal is ${stopWatch.elapsedTime}ms`); } - public getDeactivateScriptLocation(_shell: string): string { - return path.join(_SCRIPTS_DIR); + public getDeactivateScriptLocation(shell: string): string | undefined { + const shellType = identifyShellFromShellPath(shell); + if (!ShellIntegrationShells.includes(shellType)) { + return undefined; + } + return path.join(_SCRIPTS_DIR, 'deactivate', shellType); } } diff --git a/src/client/terminals/envCollectionActivation/service.ts b/src/client/terminals/envCollectionActivation/service.ts index 5831bc560fba..bb8b0feb513b 100644 --- a/src/client/terminals/envCollectionActivation/service.ts +++ b/src/client/terminals/envCollectionActivation/service.ts @@ -209,14 +209,18 @@ export class TerminalEnvVarCollectionService implements IExtensionActivationServ // Prefer prepending to PATH instead of replacing it, as we do not want to replace any // changes to PATH users might have made it in their init scripts (~/.bashrc etc.) const prependedPart = env.PATH.slice(0, -processEnv.PATH.length); - value = `${deactivate}${this.separator}${prependedPart}`; + if (deactivate) { + value = `${deactivate}${this.separator}${prependedPart}`; + } traceVerbose(`Prepending environment variable ${key} in collection with ${value}`); envVarCollection.prepend(key, value, prependOptions); } else { if (!value.endsWith(this.separator)) { value = value.concat(this.separator); } - value = `${deactivate}${this.separator}${value}`; + if (deactivate) { + value = `${deactivate}${this.separator}${value}`; + } traceVerbose(`Prepending environment variable ${key} in collection to ${value}`); envVarCollection.prepend(key, value, prependOptions); } diff --git a/src/client/terminals/serviceRegistry.ts b/src/client/terminals/serviceRegistry.ts index bd94ecd644e5..26ac2d90d614 100644 --- a/src/client/terminals/serviceRegistry.ts +++ b/src/client/terminals/serviceRegistry.ts @@ -18,7 +18,6 @@ import { } from './types'; import { TerminalEnvVarCollectionService } from './envCollectionActivation/service'; import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; -import { TerminalDeactivateLimitationPrompt } from './envCollectionActivation/deactivatePrompt'; import { TerminalIndicatorPrompt } from './envCollectionActivation/indicatorPrompt'; import { TerminalDeactivateService } from './envCollectionActivation/deactivateService'; @@ -49,9 +48,5 @@ export function registerTypes(serviceManager: IServiceManager): void { IExtensionSingleActivationService, TerminalIndicatorPrompt, ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - TerminalDeactivateLimitationPrompt, - ); serviceManager.addBinding(ITerminalEnvVarCollectionService, IExtensionActivationService); } diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 5bca7b6c895e..782a5e92f05a 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -45,5 +45,5 @@ export interface ITerminalEnvVarCollectionService { export const ITerminalDeactivateService = Symbol('ITerminalDeactivateService'); export interface ITerminalDeactivateService { getTerminalProcessVariables(shell: string): Promise; - getDeactivateScriptLocation(shell: string): string; + getDeactivateScriptLocation(shell: string): string | undefined; } diff --git a/src/test/terminals/envCollectionActivation/deactivatePrompt.unit.test.ts b/src/test/terminals/envCollectionActivation/deactivatePrompt.unit.test.ts deleted file mode 100644 index f775241abb32..000000000000 --- a/src/test/terminals/envCollectionActivation/deactivatePrompt.unit.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { mock, when, anything, instance, verify, reset } from 'ts-mockito'; -import { EventEmitter, Terminal, TerminalDataWriteEvent, TextDocument, TextEditor, Uri } from 'vscode'; -import * as sinon from 'sinon'; -import { expect } from 'chai'; -import { - IApplicationEnvironment, - IApplicationShell, - IDocumentManager, - ITerminalManager, -} from '../../../client/common/application/types'; -import { IExperimentService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; -import { Common, Interpreters } from '../../../client/common/utils/localize'; -import { TerminalEnvVarActivation } from '../../../client/common/experiments/groups'; -import { sleep } from '../../core'; -import { IInterpreterService } from '../../../client/interpreter/contracts'; -import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; -import { TerminalDeactivateLimitationPrompt } from '../../../client/terminals/envCollectionActivation/deactivatePrompt'; -import { PythonEnvType } from '../../../client/pythonEnvironments/base/info'; -import { TerminalShellType } from '../../../client/common/terminal/types'; -import * as processApi from '../../../client/common/process/rawProcessApis'; -import * as fsapi from '../../../client/common/platform/fs-paths'; -import { noop } from '../../../client/common/utils/misc'; - -suite('Terminal Deactivation Limitation Prompt', () => { - let shell: IApplicationShell; - let experimentService: IExperimentService; - let persistentStateFactory: IPersistentStateFactory; - let appEnvironment: IApplicationEnvironment; - let deactivatePrompt: TerminalDeactivateLimitationPrompt; - let terminalWriteEvent: EventEmitter; - let notificationEnabled: IPersistentState; - let interpreterService: IInterpreterService; - let terminalManager: ITerminalManager; - let documentManager: IDocumentManager; - const prompts = [Common.editSomething.format('~/.bashrc'), Common.doNotShowAgain]; - const expectedMessage = Interpreters.terminalDeactivatePrompt.format('~/.bashrc'); - const initScriptPath = 'home/node/.bashrc'; - const resource = Uri.file('a'); - let terminal: Terminal; - - setup(async () => { - const activeEditorEvent = new EventEmitter(); - const document = ({ - uri: Uri.file(''), - getText: () => '', - } as unknown) as TextDocument; - sinon.stub(processApi, 'shellExec').callsFake(async (command: string) => { - if (command !== 'echo ~/.bashrc') { - throw new Error(`Unexpected command: ${command}`); - } - await sleep(1500); - return { stdout: initScriptPath }; - }); - documentManager = mock(); - terminalManager = mock(); - terminal = ({ - creationOptions: { cwd: resource }, - processId: Promise.resolve(1), - dispose: noop, - show: noop, - sendText: noop, - } as unknown) as Terminal; - when(terminalManager.createTerminal(anything())).thenReturn(terminal); - when(documentManager.openTextDocument(initScriptPath)).thenReturn(Promise.resolve(document)); - when(documentManager.onDidChangeActiveTextEditor).thenReturn(activeEditorEvent.event); - shell = mock(); - interpreterService = mock(); - experimentService = mock(); - persistentStateFactory = mock(); - appEnvironment = mock(); - when(appEnvironment.shell).thenReturn('bash'); - notificationEnabled = mock>(); - terminalWriteEvent = new EventEmitter(); - when(persistentStateFactory.createGlobalPersistentState(anything(), true)).thenReturn( - instance(notificationEnabled), - ); - when(shell.onDidWriteTerminalData).thenReturn(terminalWriteEvent.event); - when(experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)).thenReturn(true); - deactivatePrompt = new TerminalDeactivateLimitationPrompt( - instance(shell), - instance(persistentStateFactory), - [], - instance(interpreterService), - instance(appEnvironment), - instance(documentManager), - instance(terminalManager), - instance(experimentService), - ); - }); - - teardown(() => { - sinon.restore(); - }); - - test('Show notification when "deactivate" command is run when a virtual env is selected', async () => { - when(notificationEnabled.value).thenReturn(true); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenResolve(undefined); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(shell.showWarningMessage(expectedMessage, ...prompts)).once(); - }); - - test('When using cmd, do not show notification for the same', async () => { - reset(appEnvironment); - when(appEnvironment.shell).thenReturn(TerminalShellType.commandPrompt); - when(notificationEnabled.value).thenReturn(true); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenResolve(undefined); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(shell.showWarningMessage(expectedMessage, ...prompts)).never(); - }); - - test('When not in experiment, do not show notification for the same', async () => { - reset(experimentService); - when(experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)).thenReturn(false); - - when(notificationEnabled.value).thenReturn(true); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenResolve(undefined); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(shell.showWarningMessage(expectedMessage, ...prompts)).never(); - }); - - test('Do not show notification if notification is disabled', async () => { - when(notificationEnabled.value).thenReturn(false); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenResolve(undefined); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(shell.showWarningMessage(expectedMessage, ...prompts)).never(); - }); - - test('Do not show notification when virtual env is not activated for terminal', async () => { - when(notificationEnabled.value).thenReturn(true); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Conda, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenResolve(undefined); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(shell.showWarningMessage(expectedMessage, ...prompts)).never(); - }); - - test("Disable notification if `Don't show again` is clicked", async () => { - when(notificationEnabled.value).thenReturn(true); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(({ - type: PythonEnvType.Virtual, - } as unknown) as PythonEnvironment); - when(shell.showWarningMessage(expectedMessage, ...prompts)).thenReturn(Promise.resolve(Common.doNotShowAgain)); - - await deactivatePrompt.activate(); - terminalWriteEvent.fire({ data: 'Please deactivate me', terminal }); - await sleep(1); - - verify(notificationEnabled.updateValue(false)).once(); - }); - - test('Edit script correctly if `Edit