From 0228131a667737e832e076ffc1834eb718ca8d91 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 18 Jan 2023 19:09:54 +0530 Subject: [PATCH 01/14] Detect if VSCode is launched from an activated environment and select it --- src/client/common/utils/localize.ts | 5 + src/client/interpreter/contracts.ts | 5 + src/client/interpreter/interpreterService.ts | 3 + src/client/interpreter/serviceRegistry.ts | 8 +- .../virtualEnvs/activatedEnvLaunch.ts | 143 ++++++++++++++++++ src/client/telemetry/constants.ts | 1 + src/client/telemetry/index.ts | 16 ++ .../interpreters/serviceRegistry.unit.test.ts | 2 + 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 9928a15194e7..2d8272e47c85 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -191,7 +191,12 @@ export namespace Interpreters { export const condaInheritEnvMessage = l10n.t( 'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings.', ); + export const activatedCondaEnvLaunch = l10n.t( + 'Interpreters.activatedCondaEnvLaunch', + 'We noticed VSCode was launched from an activated conda environment, would you like to select it?', + ); export const environmentPromptMessage = l10n.t( + 'Interpreters.environmentPromptMessage', 'We noticed a new environment has been created. Do you want to select it for the workspace folder?', ); export const entireWorkspace = l10n.t('Select at workspace level'); diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index a79a5250ec99..0bd2ffcfc99d 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -122,3 +122,8 @@ export type WorkspacePythonPath = { folderUri: Uri; configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder; }; + +export const IActivatedEnvironmentLaunch = Symbol('IActivatedEnvironmentLaunch'); +export interface IActivatedEnvironmentLaunch { + selectIfLaunchedViaActivatedEnv(): Promise; +} diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 50545558d721..121ac5b0ea9a 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -22,6 +22,7 @@ import { import { IServiceContainer } from '../ioc/types'; import { PythonEnvironment } from '../pythonEnvironments/info'; import { + IActivatedEnvironmentLaunch, IComponentAdapter, IInterpreterDisplay, IInterpreterService, @@ -93,6 +94,8 @@ export class InterpreterService implements Disposable, IInterpreterService { } public async refresh(resource?: Uri): Promise { + const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); + await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); await interpreterDisplay.refresh(resource); this.ensureEnvironmentContainsPython(this.configService.getSettings(resource).pythonPath).ignoreErrors(); diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index cdcd8718fd1d..6caaa2cc4170 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -25,11 +25,12 @@ import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager, } from './configuration/types'; -import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService } from './contracts'; +import { IActivatedEnvironmentLaunch, IInterpreterDisplay, IInterpreterHelper, IInterpreterService } from './contracts'; import { InterpreterDisplay } from './display'; import { InterpreterLocatorProgressStatubarHandler } from './display/progressDisplay'; import { InterpreterHelper } from './helpers'; import { InterpreterService } from './interpreterService'; +import { ActivatedEnvironmentLaunch } from './virtualEnvs/activatedEnvLaunch'; import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; @@ -90,6 +91,11 @@ export function registerInterpreterTypes(serviceManager: IServiceManager): void ); serviceManager.addSingleton(IExtensionActivationService, CondaInheritEnvPrompt); + serviceManager.addSingleton( + IExtensionSingleActivationService, + ActivatedEnvironmentLaunch, + ); + serviceManager.addSingleton(IActivatedEnvironmentLaunch, ActivatedEnvironmentLaunch); } export function registerTypes(serviceManager: IServiceManager): void { diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts new file mode 100644 index 000000000000..408f4d031688 --- /dev/null +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable, optional } from 'inversify'; +import { ConfigurationTarget } from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { IProcessServiceFactory } from '../../common/process/types'; +import { Common, Interpreters } from '../../common/utils/localize'; +import { traceError, traceWarn } from '../../logging'; +import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { IPythonPathUpdaterServiceManager } from '../configuration/types'; +import { IInterpreterService } from '../contracts'; + +@injectable() +export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + constructor( + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IPythonPathUpdaterServiceManager) + private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, + @optional() public wasTriggered: boolean = false, + ) {} + + public async activate(): Promise { + this.initializeInBackground().ignoreErrors(); + } + + public async initializeInBackground(): Promise { + if (this.workspaceService.workspaceFile) { + // Assuming multiroot workspaces cannot be directly launched via `code .` command. + return; + } + if (this.wasTriggered) { + return; + } + const baseCondaPrefix = getPrefixOfActivatedCondaEnv(); + if (!baseCondaPrefix) { + return; + } + const info = await this.interpreterService.getInterpreterDetails(baseCondaPrefix); + if (info?.envName !== 'base') { + // Only show prompt for base conda environments, as we need to check config for such envs which can be slow. + return; + } + const conda = await Conda.getConda(); + if (!conda) { + traceWarn('Conda not found even though activated environment vars are set'); + return; + } + const service = await this.processServiceFactory.create(); + const autoActivateBaseConfig = await service + .shellExec(`${conda.shellCommand} config --get auto_activate_base`) + .catch((ex) => { + traceError(ex); + return { stdout: '' }; + }); + if (autoActivateBaseConfig.stdout.trim().toLowerCase().endsWith('false')) { + await this.promptAndUpdate(baseCondaPrefix); + } + } + + private async promptAndUpdate(prefix: string) { + this.wasTriggered = true; + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; + const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; + const selection = await this.appShell.showInformationMessage(Interpreters.activatedCondaEnvLaunch, ...prompts); + sendTelemetryEvent(EventName.ACTIVATED_CONDA_ENV_LAUNCH, undefined, { + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, + }); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await this.setPrefixAsInterpeter(prefix); + } + } + + public async selectIfLaunchedViaActivatedEnv(): Promise { + const prefix = await this.getPrefixOfActivatedEnv(); + if (!prefix) { + return; + } + this.wasTriggered = true; + await this.setPrefixAsInterpeter(prefix); + } + + private async setPrefixAsInterpeter(prefix: string) { + if (this.workspaceService.workspaceFile) { + return; + } + const { workspaceFolders } = this.workspaceService; + if (!workspaceFolders || workspaceFolders.length === 0) { + await this.pythonPathUpdaterService.updatePythonPath(prefix, ConfigurationTarget.Global, 'load'); + } else { + await this.pythonPathUpdaterService.updatePythonPath( + prefix, + ConfigurationTarget.WorkspaceFolder, + 'load', + workspaceFolders[0].uri, + ); + } + } + + private async getPrefixOfActivatedEnv(): Promise { + const virtualEnvVar = process.env.VIRTUAL_ENV; + if (virtualEnvVar !== undefined && virtualEnvVar.length > 0) { + return virtualEnvVar; + } + const condaPrefixVar = getPrefixOfActivatedCondaEnv(); + if (!condaPrefixVar) { + return undefined; + } + const info = await this.interpreterService.getInterpreterDetails(condaPrefixVar); + if (info?.envName !== 'base') { + return condaPrefixVar; + } + // Ignoring base conda environments, as they could be automatically set by conda. + if (process.env.CONDA_AUTO_ACTIVATE_BASE !== undefined) { + if (process.env.CONDA_AUTO_ACTIVATE_BASE.toLowerCase() === 'false') { + return condaPrefixVar; + } + } + return undefined; + } +} + +function getPrefixOfActivatedCondaEnv() { + const condaPrefixVar = process.env.CONDA_PREFIX; + if (condaPrefixVar && condaPrefixVar.length > 0) { + const condaShlvl = process.env.CONDA_SHLVL; + if (condaShlvl !== undefined && condaShlvl.length > 0 && condaShlvl > '0') { + return condaPrefixVar; + } + } + return undefined; +} diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index de97d817cb88..4a895ab8a9ff 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -30,6 +30,7 @@ export enum EventName { PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT', PYTHON_NOT_INSTALLED_PROMPT = 'PYTHON_NOT_INSTALLED_PROMPT', CONDA_INHERIT_ENV_PROMPT = 'CONDA_INHERIT_ENV_PROMPT', + ACTIVATED_CONDA_ENV_LAUNCH = 'ACTIVATED_CONDA_ENV_LAUNCH', ENVFILE_VARIABLE_SUBSTITUTION = 'ENVFILE_VARIABLE_SUBSTITUTION', ENVFILE_WORKSPACE = 'ENVFILE_WORKSPACE', EXECUTION_CODE = 'EXECUTION_CODE', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 819a019689e2..84d20273039e 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1315,6 +1315,22 @@ export interface IEventNamePropertyMapping { */ selection: 'Yes' | 'No' | 'More Info' | undefined; }; + /** + * Telemetry event sent with details when user clicks the prompt with the following message + * `Prompt message` :- 'We noticed VSCode was launched from an activated conda environment, would you like to select it?' + */ + /* __GDPR__ + "conda_inherit_env_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karrtikr" } + } + */ + [EventName.ACTIVATED_CONDA_ENV_LAUNCH]: { + /** + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected + */ + selection: 'Yes' | 'No' | undefined; + }; /** * Telemetry event sent with details when user clicks a button in the virtual environment prompt. * `Prompt message` :- 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?' diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index dff756cd3e64..4164d12972f6 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -34,6 +34,7 @@ import { InterpreterLocatorProgressStatubarHandler } from '../../client/interpre import { InterpreterHelper } from '../../client/interpreter/helpers'; import { InterpreterService } from '../../client/interpreter/interpreterService'; import { registerTypes } from '../../client/interpreter/serviceRegistry'; +import { ActivatedEnvironmentLaunch } from '../../client/interpreter/virtualEnvs/activatedEnvLaunch'; import { CondaInheritEnvPrompt } from '../../client/interpreter/virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentPrompt } from '../../client/interpreter/virtualEnvs/virtualEnvPrompt'; import { ServiceManager } from '../../client/ioc/serviceManager'; @@ -69,6 +70,7 @@ suite('Interpreters - Service Registry', () => { [EnvironmentActivationService, EnvironmentActivationService], [IEnvironmentActivationService, EnvironmentActivationService], [IExtensionActivationService, CondaInheritEnvPrompt], + [IExtensionSingleActivationService, ActivatedEnvironmentLaunch], ].forEach((mapping) => { // eslint-disable-next-line prefer-spread verify(serviceManager.addSingleton.apply(serviceManager, mapping as never)).once(); From a1137dff066ec737f2a203fbbf95b2dc5f9c6720 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 18 Jan 2023 19:10:50 +0530 Subject: [PATCH 02/14] Only build VSIX --- .github/workflows/pr-check.yml | 465 --------------------------------- 1 file changed, 465 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index ab615f33b888..6c489b82f265 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -33,468 +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@v3 - - - 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@v3 - - - 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: - working-directory: 'pythonFiles' - - ### Non-smoke tests - 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, python-unit, venv, single-workspace, debugger, functional] - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - path: ${{ env.special-working-directory-relative }} - - - name: Install Node - uses: actions/setup-node@v3 - 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: 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' - if: startsWith(matrix.python, 3.) - - - 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' - if: startsWith(matrix.python, 3.) - - - 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/debugger-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.) - - # Run the Python tests in our codebase. - - name: Run Python unit tests - run: | - python pythonFiles/tests/run_all.py - if: matrix.test-suite == 'python-unit' - - # 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 multi-workspace tests - env: - CI_PYTHON_VERSION: ${{ matrix.python }} - uses: GabrielBB/xvfb-action@v1.6 - with: - run: npm run testMultiWorkspace - working-directory: ${{ env.special-working-directory }} - if: matrix.test-suite == 'multi-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@v3 - - - 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@v3 - - - name: Install Node - uses: actions/setup-node@v3 - 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 b0d98de61091d369698d91ebcf64f192279e2c95 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 19 Jan 2023 14:38:54 +0530 Subject: [PATCH 03/14] More changes --- src/client/activation/activationManager.ts | 3 +++ src/client/common/persistentState.ts | 12 ++++++++++-- src/client/common/process/pythonExecutionFactory.ts | 6 +++++- src/client/common/utils/localize.ts | 4 +--- src/client/interpreter/interpreterService.ts | 8 ++++++-- .../interpreter/virtualEnvs/activatedEnvLaunch.ts | 6 +++++- src/test/activation/activationManager.unit.test.ts | 7 +++++++ 7 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/client/activation/activationManager.ts b/src/client/activation/activationManager.ts index fac5cbeda648..c7c4478333a6 100644 --- a/src/client/activation/activationManager.ts +++ b/src/client/activation/activationManager.ts @@ -12,6 +12,7 @@ import { IFileSystem } from '../common/platform/types'; import { IDisposable, IInterpreterPathService, Resource } from '../common/types'; import { Deferred } from '../common/utils/async'; import { IInterpreterAutoSelectionService } from '../interpreter/autoSelection/types'; +import { IActivatedEnvironmentLaunch } from '../interpreter/contracts'; import { traceDecoratorError } from '../logging'; import { sendActivationTelemetry } from '../telemetry/envFileTelemetry'; import { IExtensionActivationManager, IExtensionActivationService, IExtensionSingleActivationService } from './types'; @@ -37,6 +38,7 @@ export class ExtensionActivationManager implements IExtensionActivationManager { @inject(IFileSystem) private readonly fileSystem: IFileSystem, @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService, @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService, + @inject(IActivatedEnvironmentLaunch) private readonly activatedEnvLaunch: IActivatedEnvironmentLaunch, ) {} private filterServices() { @@ -91,6 +93,7 @@ export class ExtensionActivationManager implements IExtensionActivationManager { if (this.workspaceService.isTrusted) { // Do not interact with interpreters in a untrusted workspace. + await this.activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); await this.autoSelection.autoSelectInterpreter(resource); await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource); } diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index 0d397665d96d..2d84c2117934 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -6,7 +6,7 @@ import { inject, injectable, named } from 'inversify'; import { Memento } from 'vscode'; import { IExtensionSingleActivationService } from '../activation/types'; -import { traceError } from '../logging'; +import { traceError, traceVerbose, traceWarn } from '../logging'; import { ICommandManager } from './application/types'; import { Commands } from './constants'; import { @@ -41,13 +41,21 @@ export class PersistentState implements IPersistentState { } } - public async updateValue(newValue: T): Promise { + public async updateValue(newValue: T, retryOnce = true): Promise { try { if (this.expiryDurationMs) { await this.storage.update(this.key, { data: newValue, expiry: Date.now() + this.expiryDurationMs }); } else { await this.storage.update(this.key, newValue); } + if (retryOnce && JSON.stringify(this.value) != JSON.stringify(newValue)) { + traceWarn('Storage update failed for key', this.key, ' retrying'); + await this.updateValue(undefined as any, false); + await this.updateValue(newValue, false); + if (JSON.stringify(this.value) != JSON.stringify(newValue)) { + traceError('Retry failed, storage update failed for key', this.key); + } + } } catch (ex) { traceError('Error while updating storage for key:', this.key, ex); } diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 658ab86dcddf..fc13e7f2346c 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -3,7 +3,7 @@ import { inject, injectable } from 'inversify'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; -import { IComponentAdapter } from '../../interpreter/contracts'; +import { IActivatedEnvironmentLaunch, IComponentAdapter } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; @@ -52,6 +52,10 @@ export class PythonExecutionFactory implements IPythonExecutionFactory { public async create(options: ExecutionFactoryCreationOptions): Promise { let { pythonPath } = options; if (!pythonPath || pythonPath === 'python') { + const activatedEnvLaunch = this.serviceContainer.get( + IActivatedEnvironmentLaunch, + ); + await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); // If python path wasn't passed in, we need to auto select it and then read it // from the configuration. const interpreterPath = this.interpreterPathExpHelper.get(options.resource); diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 2d8272e47c85..a5c0222a71fc 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -192,11 +192,9 @@ export namespace Interpreters { 'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings.', ); export const activatedCondaEnvLaunch = l10n.t( - 'Interpreters.activatedCondaEnvLaunch', - 'We noticed VSCode was launched from an activated conda environment, would you like to select it?', + 'We noticed VS Code was launched from an activated conda environment, would you like to select it?', ); export const environmentPromptMessage = l10n.t( - 'Interpreters.environmentPromptMessage', 'We noticed a new environment has been created. Do you want to select it for the workspace folder?', ); export const entireWorkspace = l10n.t('Select at workspace level'); diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 121ac5b0ea9a..757f3849383c 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -94,8 +94,6 @@ export class InterpreterService implements Disposable, IInterpreterService { } public async refresh(resource?: Uri): Promise { - const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); - await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); await interpreterDisplay.refresh(resource); this.ensureEnvironmentContainsPython(this.configService.getSettings(resource).pythonPath).ignoreErrors(); @@ -182,6 +180,12 @@ export class InterpreterService implements Disposable, IInterpreterService { } public async getActiveInterpreter(resource?: Uri): Promise { + const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); + await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + // Config service also updates itself on interpreter config change, + // so yielding control here to make sure it goes first and updates + // itself before we can query it. + await sleep(1); let path = this.configService.getSettings(resource).pythonPath; if (pathUtils.basename(path) === path) { // Value can be `python`, `python3`, `python3.9` etc. diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index 408f4d031688..5cd6ea7c81a0 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -6,6 +6,8 @@ import { ConfigurationTarget } from 'vscode'; import { IExtensionSingleActivationService } from '../../activation/types'; import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; import { IProcessServiceFactory } from '../../common/process/types'; +import { sleep } from '../../common/utils/async'; +import { cache } from '../../common/utils/decorators'; import { Common, Interpreters } from '../../common/utils/localize'; import { traceError, traceWarn } from '../../logging'; import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; @@ -82,13 +84,15 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer } } + @cache(-1, true) public async selectIfLaunchedViaActivatedEnv(): Promise { + this.wasTriggered = true; const prefix = await this.getPrefixOfActivatedEnv(); if (!prefix) { return; } - this.wasTriggered = true; await this.setPrefixAsInterpeter(prefix); + await sleep(1); } private async setPrefixAsInterpeter(prefix: string) { diff --git a/src/test/activation/activationManager.unit.test.ts b/src/test/activation/activationManager.unit.test.ts index 2b8d54f12ee9..a3eeb7a48012 100644 --- a/src/test/activation/activationManager.unit.test.ts +++ b/src/test/activation/activationManager.unit.test.ts @@ -18,6 +18,7 @@ import { FileSystem } from '../../client/common/platform/fileSystem'; import { IFileSystem } from '../../client/common/platform/types'; import { IDisposable, IInterpreterPathService } from '../../client/common/types'; import { IInterpreterAutoSelectionService } from '../../client/interpreter/autoSelection/types'; +import { IActivatedEnvironmentLaunch } from '../../client/interpreter/contracts'; import * as EnvFileTelemetry from '../../client/telemetry/envFileTelemetry'; import { sleep } from '../core'; @@ -43,8 +44,11 @@ suite('Activation Manager', () => { let activeResourceService: IActiveResourceService; let documentManager: typemoq.IMock; let interpreterPathService: typemoq.IMock; + let activatedEnvLaunch: typemoq.IMock; let fileSystem: IFileSystem; setup(() => { + activatedEnvLaunch = typemoq.Mock.ofType(); + activatedEnvLaunch.setup((a) => a.selectIfLaunchedViaActivatedEnv()).returns(() => Promise.resolve()); interpreterPathService = typemoq.Mock.ofType(); interpreterPathService .setup((i) => i.copyOldInterpreterStorageValuesToNew(typemoq.It.isAny())) @@ -70,6 +74,7 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, + activatedEnvLaunch.object, ); sinon.stub(EnvFileTelemetry, 'sendActivationTelemetry').resolves(); @@ -102,6 +107,7 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, + activatedEnvLaunch.object, ); await managerTest.activateWorkspace(resource); @@ -132,6 +138,7 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, + activatedEnvLaunch.object, ); await managerTest.activateWorkspace(resource); From a0385d0cf7a862dfee58306e8eed0ce47151756d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 01:13:47 +0530 Subject: [PATCH 04/14] Fixes --- src/client/activation/activationManager.ts | 3 --- src/client/common/persistentState.ts | 2 +- src/client/interpreter/contracts.ts | 1 + src/client/interpreter/interpreterService.ts | 10 +++---- .../virtualEnvs/activatedEnvLaunch.ts | 27 ++++++++++++++----- .../activation/activationManager.unit.test.ts | 7 ----- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/client/activation/activationManager.ts b/src/client/activation/activationManager.ts index c7c4478333a6..fac5cbeda648 100644 --- a/src/client/activation/activationManager.ts +++ b/src/client/activation/activationManager.ts @@ -12,7 +12,6 @@ import { IFileSystem } from '../common/platform/types'; import { IDisposable, IInterpreterPathService, Resource } from '../common/types'; import { Deferred } from '../common/utils/async'; import { IInterpreterAutoSelectionService } from '../interpreter/autoSelection/types'; -import { IActivatedEnvironmentLaunch } from '../interpreter/contracts'; import { traceDecoratorError } from '../logging'; import { sendActivationTelemetry } from '../telemetry/envFileTelemetry'; import { IExtensionActivationManager, IExtensionActivationService, IExtensionSingleActivationService } from './types'; @@ -38,7 +37,6 @@ export class ExtensionActivationManager implements IExtensionActivationManager { @inject(IFileSystem) private readonly fileSystem: IFileSystem, @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService, @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService, - @inject(IActivatedEnvironmentLaunch) private readonly activatedEnvLaunch: IActivatedEnvironmentLaunch, ) {} private filterServices() { @@ -93,7 +91,6 @@ export class ExtensionActivationManager implements IExtensionActivationManager { if (this.workspaceService.isTrusted) { // Do not interact with interpreters in a untrusted workspace. - await this.activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); await this.autoSelection.autoSelectInterpreter(resource); await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource); } diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index 2d84c2117934..3594aecb71be 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -6,7 +6,7 @@ import { inject, injectable, named } from 'inversify'; import { Memento } from 'vscode'; import { IExtensionSingleActivationService } from '../activation/types'; -import { traceError, traceVerbose, traceWarn } from '../logging'; +import { traceError, traceWarn } from '../logging'; import { ICommandManager } from './application/types'; import { Commands } from './constants'; import { diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 0bd2ffcfc99d..291809f47b12 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -126,4 +126,5 @@ export type WorkspacePythonPath = { export const IActivatedEnvironmentLaunch = Symbol('IActivatedEnvironmentLaunch'); export interface IActivatedEnvironmentLaunch { selectIfLaunchedViaActivatedEnv(): Promise; + getPrefixOfActivatedEnv(): Promise; } diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 757f3849383c..e51ab72210e7 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -181,12 +181,10 @@ export class InterpreterService implements Disposable, IInterpreterService { public async getActiveInterpreter(resource?: Uri): Promise { const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); - await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); - // Config service also updates itself on interpreter config change, - // so yielding control here to make sure it goes first and updates - // itself before we can query it. - await sleep(1); - let path = this.configService.getSettings(resource).pythonPath; + let path = await activatedEnvLaunch.getPrefixOfActivatedEnv(); + if (!path) { + path = this.configService.getSettings(resource).pythonPath; + } if (pathUtils.basename(path) === path) { // Value can be `python`, `python3`, `python3.9` etc. // Note the following triggers autoselection if no interpreter is explictly diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index 5cd6ea7c81a0..8bd9d2998dc8 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -14,10 +14,10 @@ import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IPythonPathUpdaterServiceManager } from '../configuration/types'; -import { IInterpreterService } from '../contracts'; +import { IActivatedEnvironmentLaunch, IInterpreterService } from '../contracts'; @injectable() -export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationService { +export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationService, IActivatedEnvironmentLaunch { public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @@ -27,7 +27,7 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @optional() public wasTriggered: boolean = false, + @optional() public wasSelected: boolean = false, ) {} public async activate(): Promise { @@ -39,7 +39,9 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer // Assuming multiroot workspaces cannot be directly launched via `code .` command. return; } - if (this.wasTriggered) { + await this.selectIfLaunchedViaActivatedEnv(); + if (this.wasSelected) { + // Return if we have already selected or prompted to select an interpreter. return; } const baseCondaPrefix = getPrefixOfActivatedCondaEnv(); @@ -69,7 +71,7 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer } private async promptAndUpdate(prefix: string) { - this.wasTriggered = true; + this.wasSelected = true; const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; const selection = await this.appShell.showInformationMessage(Interpreters.activatedCondaEnvLaunch, ...prompts); @@ -86,7 +88,6 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer @cache(-1, true) public async selectIfLaunchedViaActivatedEnv(): Promise { - this.wasTriggered = true; const prefix = await this.getPrefixOfActivatedEnv(); if (!prefix) { return; @@ -112,7 +113,19 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer } } - private async getPrefixOfActivatedEnv(): Promise { + @cache(-1, true) + public async getPrefixOfActivatedEnv(): Promise { + if (this.wasSelected) { + return undefined; + } + const prefix = await this._getPrefixOfActivatedEnv(); + if (!prefix) { + this.wasSelected = true; + } + return prefix; + } + + private async _getPrefixOfActivatedEnv(): Promise { const virtualEnvVar = process.env.VIRTUAL_ENV; if (virtualEnvVar !== undefined && virtualEnvVar.length > 0) { return virtualEnvVar; diff --git a/src/test/activation/activationManager.unit.test.ts b/src/test/activation/activationManager.unit.test.ts index a3eeb7a48012..2b8d54f12ee9 100644 --- a/src/test/activation/activationManager.unit.test.ts +++ b/src/test/activation/activationManager.unit.test.ts @@ -18,7 +18,6 @@ import { FileSystem } from '../../client/common/platform/fileSystem'; import { IFileSystem } from '../../client/common/platform/types'; import { IDisposable, IInterpreterPathService } from '../../client/common/types'; import { IInterpreterAutoSelectionService } from '../../client/interpreter/autoSelection/types'; -import { IActivatedEnvironmentLaunch } from '../../client/interpreter/contracts'; import * as EnvFileTelemetry from '../../client/telemetry/envFileTelemetry'; import { sleep } from '../core'; @@ -44,11 +43,8 @@ suite('Activation Manager', () => { let activeResourceService: IActiveResourceService; let documentManager: typemoq.IMock; let interpreterPathService: typemoq.IMock; - let activatedEnvLaunch: typemoq.IMock; let fileSystem: IFileSystem; setup(() => { - activatedEnvLaunch = typemoq.Mock.ofType(); - activatedEnvLaunch.setup((a) => a.selectIfLaunchedViaActivatedEnv()).returns(() => Promise.resolve()); interpreterPathService = typemoq.Mock.ofType(); interpreterPathService .setup((i) => i.copyOldInterpreterStorageValuesToNew(typemoq.It.isAny())) @@ -74,7 +70,6 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, - activatedEnvLaunch.object, ); sinon.stub(EnvFileTelemetry, 'sendActivationTelemetry').resolves(); @@ -107,7 +102,6 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, - activatedEnvLaunch.object, ); await managerTest.activateWorkspace(resource); @@ -138,7 +132,6 @@ suite('Activation Manager', () => { instance(fileSystem), instance(activeResourceService), interpreterPathService.object, - activatedEnvLaunch.object, ); await managerTest.activateWorkspace(resource); From 76c35ddbf4f688935d847bf6a443179c916b5c11 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 01:24:08 +0530 Subject: [PATCH 05/14] Simplify --- src/client/interpreter/contracts.ts | 3 +- src/client/interpreter/interpreterService.ts | 2 +- .../virtualEnvs/activatedEnvLaunch.ts | 32 +++++++++---------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 291809f47b12..ec504802bcfc 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -125,6 +125,5 @@ export type WorkspacePythonPath = { export const IActivatedEnvironmentLaunch = Symbol('IActivatedEnvironmentLaunch'); export interface IActivatedEnvironmentLaunch { - selectIfLaunchedViaActivatedEnv(): Promise; - getPrefixOfActivatedEnv(): Promise; + selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection?: boolean): Promise; } diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index e51ab72210e7..7e5d69da4278 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -181,7 +181,7 @@ export class InterpreterService implements Disposable, IInterpreterService { public async getActiveInterpreter(resource?: Uri): Promise { const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); - let path = await activatedEnvLaunch.getPrefixOfActivatedEnv(); + let path = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(true); if (!path) { path = this.configService.getSettings(resource).pythonPath; } diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index 8bd9d2998dc8..4605a7547460 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -87,13 +87,22 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer } @cache(-1, true) - public async selectIfLaunchedViaActivatedEnv(): Promise { - const prefix = await this.getPrefixOfActivatedEnv(); + public async selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { + if (this.wasSelected) { + return undefined; + } + const prefix = await this.getPrefixOfSelectedActivatedEnv(); if (!prefix) { - return; + return undefined; + } + this.wasSelected = true; + if (doNotBlockOnSelection) { + this.setPrefixAsInterpeter(prefix).ignoreErrors(); + } else { + await this.setPrefixAsInterpeter(prefix); + await sleep(1); // Yield control so config service can update itself. } - await this.setPrefixAsInterpeter(prefix); - await sleep(1); + return prefix; } private async setPrefixAsInterpeter(prefix: string) { @@ -114,18 +123,7 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer } @cache(-1, true) - public async getPrefixOfActivatedEnv(): Promise { - if (this.wasSelected) { - return undefined; - } - const prefix = await this._getPrefixOfActivatedEnv(); - if (!prefix) { - this.wasSelected = true; - } - return prefix; - } - - private async _getPrefixOfActivatedEnv(): Promise { + public async getPrefixOfSelectedActivatedEnv(): Promise { const virtualEnvVar = process.env.VIRTUAL_ENV; if (virtualEnvVar !== undefined && virtualEnvVar.length > 0) { return virtualEnvVar; From 2e49132505e5b9a4cb90503ee02d1f8292d6a64f Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 18:46:10 +0530 Subject: [PATCH 06/14] Fixes --- src/client/common/persistentState.ts | 6 +-- src/client/interpreter/serviceRegistry.ts | 4 -- .../virtualEnvs/activatedEnvLaunch.ts | 43 +++++++++++-------- .../interpreters/serviceRegistry.unit.test.ts | 9 +++- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index 3594aecb71be..aa7f5100e785 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -6,7 +6,7 @@ import { inject, injectable, named } from 'inversify'; import { Memento } from 'vscode'; import { IExtensionSingleActivationService } from '../activation/types'; -import { traceError, traceWarn } from '../logging'; +import { traceError, traceVerbose, traceWarn } from '../logging'; import { ICommandManager } from './application/types'; import { Commands } from './constants'; import { @@ -49,11 +49,11 @@ export class PersistentState implements IPersistentState { await this.storage.update(this.key, newValue); } if (retryOnce && JSON.stringify(this.value) != JSON.stringify(newValue)) { - traceWarn('Storage update failed for key', this.key, ' retrying'); + traceVerbose('Storage update failed for key', this.key, ' retrying'); await this.updateValue(undefined as any, false); await this.updateValue(newValue, false); if (JSON.stringify(this.value) != JSON.stringify(newValue)) { - traceError('Retry failed, storage update failed for key', this.key); + traceWarn('Retry failed, storage update failed for key', this.key); } } } catch (ex) { diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 6caaa2cc4170..422776bd5e43 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -91,10 +91,6 @@ export function registerInterpreterTypes(serviceManager: IServiceManager): void ); serviceManager.addSingleton(IExtensionActivationService, CondaInheritEnvPrompt); - serviceManager.addSingleton( - IExtensionSingleActivationService, - ActivatedEnvironmentLaunch, - ); serviceManager.addSingleton(IActivatedEnvironmentLaunch, ActivatedEnvironmentLaunch); } diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index 4605a7547460..cafbc135b581 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -3,13 +3,13 @@ import { inject, injectable, optional } from 'inversify'; import { ConfigurationTarget } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; +import * as path from 'path'; import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; import { IProcessServiceFactory } from '../../common/process/types'; import { sleep } from '../../common/utils/async'; import { cache } from '../../common/utils/decorators'; import { Common, Interpreters } from '../../common/utils/localize'; -import { traceError, traceWarn } from '../../logging'; +import { traceError, traceLog, traceWarn } from '../../logging'; import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; @@ -17,9 +17,11 @@ import { IPythonPathUpdaterServiceManager } from '../configuration/types'; import { IActivatedEnvironmentLaunch, IInterpreterService } from '../contracts'; @injectable() -export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationService, IActivatedEnvironmentLaunch { +export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + private inMemorySelection: string | undefined; + constructor( @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @@ -30,20 +32,11 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer @optional() public wasSelected: boolean = false, ) {} - public async activate(): Promise { - this.initializeInBackground().ignoreErrors(); - } - - public async initializeInBackground(): Promise { + private async promptIfApplicable(): Promise { if (this.workspaceService.workspaceFile) { // Assuming multiroot workspaces cannot be directly launched via `code .` command. return; } - await this.selectIfLaunchedViaActivatedEnv(); - if (this.wasSelected) { - // Return if we have already selected or prompted to select an interpreter. - return; - } const baseCondaPrefix = getPrefixOfActivatedCondaEnv(); if (!baseCondaPrefix) { return; @@ -82,30 +75,42 @@ export class ActivatedEnvironmentLaunch implements IExtensionSingleActivationSer return; } if (selection === prompts[0]) { - await this.setPrefixAsInterpeter(prefix); + await this.setInterpeterInStorage(prefix); } } - @cache(-1, true) public async selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { if (this.wasSelected) { - return undefined; + return this.inMemorySelection; } + return this._selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection); + } + + @cache(-1, true) + private async _selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { const prefix = await this.getPrefixOfSelectedActivatedEnv(); if (!prefix) { + this.promptIfApplicable().ignoreErrors(); return undefined; } this.wasSelected = true; + this.inMemorySelection = prefix; + traceLog( + `VS Code was launched from an activated environment: '${path.basename( + prefix, + )}', selecting it as the interpreter for workspace.`, + ); if (doNotBlockOnSelection) { - this.setPrefixAsInterpeter(prefix).ignoreErrors(); + this.setInterpeterInStorage(prefix).ignoreErrors(); } else { - await this.setPrefixAsInterpeter(prefix); + await this.setInterpeterInStorage(prefix); await sleep(1); // Yield control so config service can update itself. } + this.inMemorySelection = undefined; // Once we have set the prefix in storage, clear the in memory selection. return prefix; } - private async setPrefixAsInterpeter(prefix: string) { + private async setInterpeterInStorage(prefix: string) { if (this.workspaceService.workspaceFile) { return; } diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index 4164d12972f6..00090eb4b6e9 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -28,7 +28,12 @@ import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager, } from '../../client/interpreter/configuration/types'; -import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService } from '../../client/interpreter/contracts'; +import { + IActivatedEnvironmentLaunch, + IInterpreterDisplay, + IInterpreterHelper, + IInterpreterService, +} from '../../client/interpreter/contracts'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { InterpreterLocatorProgressStatubarHandler } from '../../client/interpreter/display/progressDisplay'; import { InterpreterHelper } from '../../client/interpreter/helpers'; @@ -70,7 +75,7 @@ suite('Interpreters - Service Registry', () => { [EnvironmentActivationService, EnvironmentActivationService], [IEnvironmentActivationService, EnvironmentActivationService], [IExtensionActivationService, CondaInheritEnvPrompt], - [IExtensionSingleActivationService, ActivatedEnvironmentLaunch], + [IActivatedEnvironmentLaunch, ActivatedEnvironmentLaunch], ].forEach((mapping) => { // eslint-disable-next-line prefer-spread verify(serviceManager.addSingleton.apply(serviceManager, mapping as never)).once(); From 10168cf4aaa474e13cbf9fa438c9f665f54225cf Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 18:54:24 +0530 Subject: [PATCH 07/14] Add explanation --- src/client/interpreter/interpreterService.ts | 2 ++ .../interpreter/virtualEnvs/activatedEnvLaunch.ts | 1 + src/client/telemetry/index.ts | 12 +++++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 7e5d69da4278..59ce435bb4d8 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -182,6 +182,8 @@ export class InterpreterService implements Disposable, IInterpreterService { public async getActiveInterpreter(resource?: Uri): Promise { const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); let path = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(true); + // This is being set as interpreter in background, after which it'll show up in `.pythonPath` config. + // However we need not wait on the update to take place, as we can use the value directly. if (!path) { path = this.configService.getSettings(resource).pythonPath; } diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index cafbc135b581..ef54fd61b0a5 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -32,6 +32,7 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { @optional() public wasSelected: boolean = false, ) {} + @cache(-1, true) private async promptIfApplicable(): Promise { if (this.workspaceService.workspaceFile) { // Assuming multiroot workspaces cannot be directly launched via `code .` command. diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 84d20273039e..c833922ace30 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1299,8 +1299,9 @@ export interface IEventNamePropertyMapping { environmentsWithoutPython?: number; }; /** - * Telemetry event sent with details when user clicks the prompt with the following message - * `Prompt message` :- 'We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the "terminal.integrated.inheritEnv" setting to be changed to false. Would you like to update this setting?' + * Telemetry event sent with details when user clicks the prompt with the following message: + * + * 'We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the "terminal.integrated.inheritEnv" setting to be changed to false. Would you like to update this setting?' */ /* __GDPR__ "conda_inherit_env_prompt" : { @@ -1316,11 +1317,12 @@ export interface IEventNamePropertyMapping { selection: 'Yes' | 'No' | 'More Info' | undefined; }; /** - * Telemetry event sent with details when user clicks the prompt with the following message - * `Prompt message` :- 'We noticed VSCode was launched from an activated conda environment, would you like to select it?' + * Telemetry event sent with details when user clicks the prompt with the following message: + * + * 'We noticed VS Code was launched from an activated conda environment, would you like to select it?' */ /* __GDPR__ - "conda_inherit_env_prompt" : { + "activated_conda_env_launch" : { "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karrtikr" } } */ From 9b7cc5875bc5b6bdf6adacd9ebd22fa97260904f Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 19:13:32 +0530 Subject: [PATCH 08/14] Add comment for upstream VSCode bug --- src/client/common/persistentState.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index aa7f5100e785..48e885a676a2 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -49,7 +49,10 @@ export class PersistentState implements IPersistentState { await this.storage.update(this.key, newValue); } if (retryOnce && JSON.stringify(this.value) != JSON.stringify(newValue)) { - traceVerbose('Storage update failed for key', this.key, ' retrying'); + // Due to a VSCode bug sometimes the changes are not reflected in the storage, atleast not immediately. + // It is noticed however that if we reset the storage first and then update it, it works. + // https://github.com/microsoft/vscode/issues/171827 + traceVerbose('Storage update failed for key', this.key, ' retrying by resetting first'); await this.updateValue(undefined as any, false); await this.updateValue(newValue, false); if (JSON.stringify(this.value) != JSON.stringify(newValue)) { From 24499dad536574e528b2e56a5728fd21012d0590 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 19:40:59 +0530 Subject: [PATCH 09/14] Revert "Only build VSIX" This reverts commit a1137dff066ec737f2a203fbbf95b2dc5f9c6720. --- .github/workflows/pr-check.yml | 465 +++++++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 6c489b82f265..ab615f33b888 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -33,3 +33,468 @@ 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@v3 + + - 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@v3 + + - 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: + working-directory: 'pythonFiles' + + ### Non-smoke tests + 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, python-unit, venv, single-workspace, debugger, functional] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: ${{ env.special-working-directory-relative }} + + - name: Install Node + uses: actions/setup-node@v3 + 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: 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' + if: startsWith(matrix.python, 3.) + + - 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' + if: startsWith(matrix.python, 3.) + + - 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/debugger-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.) + + # Run the Python tests in our codebase. + - name: Run Python unit tests + run: | + python pythonFiles/tests/run_all.py + if: matrix.test-suite == 'python-unit' + + # 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 multi-workspace tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@v1.6 + with: + run: npm run testMultiWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'multi-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@v3 + + - 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@v3 + + - name: Install Node + uses: actions/setup-node@v3 + 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 138f4f4df2ca4ff57b7ce95efff86c22d5d51308 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 21:02:53 +0530 Subject: [PATCH 10/14] Fix some tests --- src/test/common/moduleInstaller.test.ts | 12 +++++++++++- .../process/pythonExecutionFactory.unit.test.ts | 12 +++++++++++- src/test/linters/lint.functional.test.ts | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index ef5f0aeb3d80..3549c47ef078 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -90,7 +90,12 @@ import { import { IMultiStepInputFactory, MultiStepInputFactory } from '../../client/common/utils/multiStepInput'; import { Architecture } from '../../client/common/utils/platform'; import { Random } from '../../client/common/utils/random'; -import { ICondaService, IInterpreterService, IComponentAdapter } from '../../client/interpreter/contracts'; +import { + ICondaService, + IInterpreterService, + IComponentAdapter, + IActivatedEnvironmentLaunch, +} from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { JupyterExtensionDependencyManager } from '../../client/jupyter/jupyterExtensionDependencyManager'; import { EnvironmentType, PythonEnvironment } from '../../client/pythonEnvironments/info'; @@ -101,6 +106,7 @@ import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; import { closeActiveWindows, initializeTest } from '../initialize'; +import { ActivatedEnvironmentLaunch } from '../../client/interpreter/virtualEnvs/activatedEnvLaunch'; chaiUse(chaiAsPromised); @@ -164,6 +170,10 @@ suite('Module Installer', () => { mockTerminalFactory.object, ); + ioc.serviceManager.addSingleton( + IActivatedEnvironmentLaunch, + ActivatedEnvironmentLaunch, + ); ioc.serviceManager.addSingleton(IModuleInstaller, PipInstaller); ioc.serviceManager.addSingleton(IModuleInstaller, CondaInstaller); ioc.serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); diff --git a/src/test/common/process/pythonExecutionFactory.unit.test.ts b/src/test/common/process/pythonExecutionFactory.unit.test.ts index b56cbaa999f1..e31a9e4d900e 100644 --- a/src/test/common/process/pythonExecutionFactory.unit.test.ts +++ b/src/test/common/process/pythonExecutionFactory.unit.test.ts @@ -26,7 +26,11 @@ import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } f import { Architecture } from '../../../client/common/utils/platform'; import { EnvironmentActivationService } from '../../../client/interpreter/activation/service'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { IComponentAdapter, IInterpreterService } from '../../../client/interpreter/contracts'; +import { + IActivatedEnvironmentLaunch, + IComponentAdapter, + IInterpreterService, +} from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; import { ServiceContainer } from '../../../client/ioc/container'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; @@ -73,6 +77,7 @@ suite('Process - PythonExecutionFactory', () => { suite(title(resource, interpreter), () => { let factory: PythonExecutionFactory; let activationHelper: IEnvironmentActivationService; + let activatedEnvironmentLaunch: IActivatedEnvironmentLaunch; let processFactory: IProcessServiceFactory; let configService: IConfigurationService; let processLogger: IProcessLogger; @@ -122,6 +127,11 @@ suite('Process - PythonExecutionFactory', () => { when(serviceContainer.get(IInterpreterService)).thenReturn( instance(interpreterService), ); + activatedEnvironmentLaunch = mock(); + when(activatedEnvironmentLaunch.selectIfLaunchedViaActivatedEnv()).thenResolve(); + when(serviceContainer.get(IActivatedEnvironmentLaunch)).thenReturn( + instance(activatedEnvironmentLaunch), + ); when(serviceContainer.get(IComponentAdapter)).thenReturn(instance(pyenvs)); when(serviceContainer.tryGet(IInterpreterService)).thenReturn( instance(interpreterService), diff --git a/src/test/linters/lint.functional.test.ts b/src/test/linters/lint.functional.test.ts index e46b4217ec1f..4c06a26067a5 100644 --- a/src/test/linters/lint.functional.test.ts +++ b/src/test/linters/lint.functional.test.ts @@ -32,7 +32,11 @@ import { } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; -import { IComponentAdapter, IInterpreterService } from '../../client/interpreter/contracts'; +import { + IActivatedEnvironmentLaunch, + IComponentAdapter, + IInterpreterService, +} from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { LINTERID_BY_PRODUCT } from '../../client/linters/constants'; import { ILintMessage, LinterId, LintMessageSeverity } from '../../client/linters/types'; @@ -650,7 +654,13 @@ class TestFixture extends BaseTestFixture { serviceContainer .setup((s) => s.get(TypeMoq.It.isValue(IComponentAdapter), TypeMoq.It.isAny())) .returns(() => componentAdapter.object); - + const activatedEnvironmentLaunch = TypeMoq.Mock.ofType(); + activatedEnvironmentLaunch + .setup((a) => a.selectIfLaunchedViaActivatedEnv()) + .returns(() => Promise.resolve(undefined)); + serviceContainer + .setup((s) => s.get(TypeMoq.It.isValue(IActivatedEnvironmentLaunch), TypeMoq.It.isAny())) + .returns(() => activatedEnvironmentLaunch.object); const platformService = new PlatformService(); super( From c6fc75eea6af75d6f06f0f6307305aaa345ea828 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 21:46:23 +0530 Subject: [PATCH 11/14] Fix single workspace test --- src/test/common/installer.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index 90378fa4e959..6c1a6383c2b6 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -100,6 +100,14 @@ import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; import { closeActiveWindows, initializeTest, IS_MULTI_ROOT_TEST, TEST_TIMEOUT } from '../initialize'; +import { IActivatedEnvironmentLaunch } from '../../client/interpreter/contracts'; +import { ActivatedEnvironmentLaunch } from '../../client/interpreter/virtualEnvs/activatedEnvLaunch'; +import { + IPythonPathUpdaterServiceFactory, + IPythonPathUpdaterServiceManager, +} from '../../client/interpreter/configuration/types'; +import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; +import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; suite('Installer', () => { let ioc: UnitTestIocContainer; @@ -169,6 +177,18 @@ suite('Installer', () => { TestFrameworkProductPathService, ProductType.TestFramework, ); + ioc.serviceManager.addSingleton( + IActivatedEnvironmentLaunch, + ActivatedEnvironmentLaunch, + ); + ioc.serviceManager.addSingleton( + IPythonPathUpdaterServiceManager, + PythonPathUpdaterService, + ); + ioc.serviceManager.addSingleton( + IPythonPathUpdaterServiceFactory, + PythonPathUpdaterServiceFactory, + ); ioc.serviceManager.addSingleton(IActiveResourceService, ActiveResourceService); ioc.serviceManager.addSingleton(IInterpreterPathService, InterpreterPathService); ioc.serviceManager.addSingleton(IExtensions, Extensions); From 5a53f922bec39e5769d4d1d862286f4efa6351ec Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 20 Jan 2023 22:52:49 +0530 Subject: [PATCH 12/14] More fixes --- src/test/common/moduleInstaller.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 3549c47ef078..302587902c16 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -1,7 +1,7 @@ import { expect, should as chaiShould, use as chaiUse } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { SemVer } from 'semver'; -import { instance, mock } from 'ts-mockito'; +import { instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri } from 'vscode'; import { IExtensionSingleActivationService } from '../../client/activation/types'; @@ -106,7 +106,6 @@ import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../testing/serviceRegistry'; import { closeActiveWindows, initializeTest } from '../initialize'; -import { ActivatedEnvironmentLaunch } from '../../client/interpreter/virtualEnvs/activatedEnvLaunch'; chaiUse(chaiAsPromised); @@ -169,10 +168,11 @@ suite('Module Installer', () => { ITerminalServiceFactory, mockTerminalFactory.object, ); - - ioc.serviceManager.addSingleton( + const activatedEnvironmentLaunch = mock(); + when(activatedEnvironmentLaunch.selectIfLaunchedViaActivatedEnv()).thenResolve(undefined); + ioc.serviceManager.addSingletonInstance( IActivatedEnvironmentLaunch, - ActivatedEnvironmentLaunch, + instance(activatedEnvironmentLaunch), ); ioc.serviceManager.addSingleton(IModuleInstaller, PipInstaller); ioc.serviceManager.addSingleton(IModuleInstaller, CondaInstaller); From 37b16b7c6ee9cf0ccc3c9d5e6f64686103265d35 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Sat, 21 Jan 2023 02:24:19 +0530 Subject: [PATCH 13/14] Add some unit tests --- .../virtualEnvs/activatedEnvLaunch.ts | 13 +- .../activatedEnvLaunch.unit.test.ts | 263 ++++++++++++++++++ 2 files changed, 269 insertions(+), 7 deletions(-) create mode 100644 src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index ef54fd61b0a5..8f445e7b1953 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -33,7 +33,7 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { ) {} @cache(-1, true) - private async promptIfApplicable(): Promise { + public async _promptIfApplicable(): Promise { if (this.workspaceService.workspaceFile) { // Assuming multiroot workspaces cannot be directly launched via `code .` command. return; @@ -89,9 +89,12 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { @cache(-1, true) private async _selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { + if (this.workspaceService.workspaceFile) { + return undefined; + } const prefix = await this.getPrefixOfSelectedActivatedEnv(); if (!prefix) { - this.promptIfApplicable().ignoreErrors(); + this._promptIfApplicable().ignoreErrors(); return undefined; } this.wasSelected = true; @@ -112,9 +115,6 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { } private async setInterpeterInStorage(prefix: string) { - if (this.workspaceService.workspaceFile) { - return; - } const { workspaceFolders } = this.workspaceService; if (!workspaceFolders || workspaceFolders.length === 0) { await this.pythonPathUpdaterService.updatePythonPath(prefix, ConfigurationTarget.Global, 'load'); @@ -128,8 +128,7 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { } } - @cache(-1, true) - public async getPrefixOfSelectedActivatedEnv(): Promise { + private async getPrefixOfSelectedActivatedEnv(): Promise { const virtualEnvVar = process.env.VIRTUAL_ENV; if (virtualEnvVar !== undefined && virtualEnvVar.length > 0) { return virtualEnvVar; diff --git a/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts b/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts new file mode 100644 index 000000000000..146d013b855a --- /dev/null +++ b/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as TypeMoq from 'typemoq'; +import { ConfigurationTarget, Uri, WorkspaceFolder } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { IPythonPathUpdaterServiceManager } from '../../../client/interpreter/configuration/types'; +import { IInterpreterService } from '../../../client/interpreter/contracts'; +import { ActivatedEnvironmentLaunch } from '../../../client/interpreter/virtualEnvs/activatedEnvLaunch'; +import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; + +suite('Activated Env Launch', async () => { + const uri = Uri.file('a'); + const condaPrefix = 'path/to/conda/env'; + const virtualEnvPrefix = 'path/to/virtual/env'; + let workspaceService: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let pythonPathUpdaterService: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let processServiceFactory: TypeMoq.IMock; + let activatedEnvLaunch: ActivatedEnvironmentLaunch; + + suite('Method getPrefixOfSelectedActivatedEnv()', () => { + const oldCondaPrefix = process.env.CONDA_PREFIX; + const oldCondaShlvl = process.env.CONDA_SHLVL; + const oldVirtualEnv = process.env.VIRTUAL_ENV; + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + pythonPathUpdaterService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + processServiceFactory = TypeMoq.Mock.ofType(); + sinon.stub(ActivatedEnvironmentLaunch.prototype, '_promptIfApplicable').returns(Promise.resolve()); + }); + + teardown(() => { + if (oldCondaPrefix) { + process.env.CONDA_PREFIX = oldCondaPrefix; + } else { + delete process.env.CONDA_PREFIX; + } + if (oldCondaShlvl) { + process.env.CONDA_SHLVL = oldCondaShlvl; + } else { + delete process.env.CONDA_SHLVL; + } + if (oldVirtualEnv) { + process.env.VIRTUAL_ENV = oldVirtualEnv; + } else { + delete process.env.VIRTUAL_ENV; + } + sinon.restore(); + }); + + test('Updates interpreter path with the non-base conda prefix if activated', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'env' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(condaPrefix, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Updates interpreter path with the base conda prefix if activated and environment var is configured to not auto activate it', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + process.env.CONDA_AUTO_ACTIVATE_BASE = 'false'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(condaPrefix, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Updates interpreter path with the base conda prefix if activated and environment var is configured to auto activate it', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + process.env.CONDA_AUTO_ACTIVATE_BASE = 'true'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(undefined, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Updates interpreter path with virtual env prefix if activated', async () => { + process.env.VIRTUAL_ENV = virtualEnvPrefix; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(virtualEnvPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(virtualEnvPrefix, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Updates interpreter path in global scope if no workspace is opened', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'env' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + workspaceService.setup((w) => w.workspaceFolders).returns(() => []); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.Global), + TypeMoq.It.isValue('load'), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(condaPrefix, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Does not update interpreter path if a multiroot workspace is opened', async () => { + process.env.VIRTUAL_ENV = virtualEnvPrefix; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => uri); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(virtualEnvPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(undefined, 'Incorrect value'); + pythonPathUpdaterService.verifyAll(); + }); + + test('Returns `undefined` if env was already selected', async () => { + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + true, + ); + const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + expect(result).to.be.equal(undefined, 'Incorrect value'); + }); + }); +}); From a8d70600b4db3dc4fa963f7060f2e765fda287de Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Sat, 21 Jan 2023 13:59:31 +0530 Subject: [PATCH 14/14] Add tests --- .../virtualEnvs/activatedEnvLaunch.ts | 5 +- .../activatedEnvLaunch.unit.test.ts | 260 +++++++++++++++++- 2 files changed, 259 insertions(+), 6 deletions(-) diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts index 8f445e7b1953..01b4829df4ac 100644 --- a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -34,10 +34,6 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { @cache(-1, true) public async _promptIfApplicable(): Promise { - if (this.workspaceService.workspaceFile) { - // Assuming multiroot workspaces cannot be directly launched via `code .` command. - return; - } const baseCondaPrefix = getPrefixOfActivatedCondaEnv(); if (!baseCondaPrefix) { return; @@ -90,6 +86,7 @@ export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { @cache(-1, true) private async _selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { if (this.workspaceService.workspaceFile) { + // Assuming multiroot workspaces cannot be directly launched via `code .` command. return undefined; } const prefix = await this.getPrefixOfSelectedActivatedEnv(); diff --git a/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts b/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts index 146d013b855a..04a5d3c95de1 100644 --- a/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts +++ b/src/test/interpreters/virtualEnvs/activatedEnvLaunch.unit.test.ts @@ -8,11 +8,13 @@ import * as sinon from 'sinon'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; -import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { ExecutionResult, IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; +import { Common } from '../../../client/common/utils/localize'; import { IPythonPathUpdaterServiceManager } from '../../../client/interpreter/configuration/types'; import { IInterpreterService } from '../../../client/interpreter/contracts'; import { ActivatedEnvironmentLaunch } from '../../../client/interpreter/virtualEnvs/activatedEnvLaunch'; import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; +import { Conda } from '../../../client/pythonEnvironments/common/environmentManagers/conda'; suite('Activated Env Launch', async () => { const uri = Uri.file('a'); @@ -23,7 +25,9 @@ suite('Activated Env Launch', async () => { let pythonPathUpdaterService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let processServiceFactory: TypeMoq.IMock; + let processService: TypeMoq.IMock; let activatedEnvLaunch: ActivatedEnvironmentLaunch; + let _promptIfApplicable: sinon.SinonStub; suite('Method getPrefixOfSelectedActivatedEnv()', () => { const oldCondaPrefix = process.env.CONDA_PREFIX; @@ -35,7 +39,8 @@ suite('Activated Env Launch', async () => { appShell = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); processServiceFactory = TypeMoq.Mock.ofType(); - sinon.stub(ActivatedEnvironmentLaunch.prototype, '_promptIfApplicable').returns(Promise.resolve()); + _promptIfApplicable = sinon.stub(ActivatedEnvironmentLaunch.prototype, '_promptIfApplicable'); + _promptIfApplicable.returns(Promise.resolve()); }); teardown(() => { @@ -153,6 +158,7 @@ suite('Activated Env Launch', async () => { const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); expect(result).to.be.equal(undefined, 'Incorrect value'); pythonPathUpdaterService.verifyAll(); + expect(_promptIfApplicable.calledOnce).to.equal(true, 'Prompt not displayed'); }); test('Updates interpreter path with virtual env prefix if activated', async () => { @@ -214,6 +220,7 @@ suite('Activated Env Launch', async () => { const result = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); expect(result).to.be.equal(condaPrefix, 'Incorrect value'); pythonPathUpdaterService.verifyAll(); + expect(_promptIfApplicable.notCalled).to.equal(true, 'Prompt should not be displayed'); }); test('Does not update interpreter path if a multiroot workspace is opened', async () => { @@ -260,4 +267,253 @@ suite('Activated Env Launch', async () => { expect(result).to.be.equal(undefined, 'Incorrect value'); }); }); + + suite('Method _promptIfApplicable()', () => { + const oldCondaPrefix = process.env.CONDA_PREFIX; + const oldCondaShlvl = process.env.CONDA_SHLVL; + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + pythonPathUpdaterService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + processServiceFactory = TypeMoq.Mock.ofType(); + processService = TypeMoq.Mock.ofType(); + processServiceFactory + .setup((p) => p.create(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(processService.object)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + processService.setup((p) => (p as any).then).returns(() => undefined); + sinon.stub(Conda, 'getConda').resolves(new Conda('conda')); + }); + + teardown(() => { + if (oldCondaPrefix) { + process.env.CONDA_PREFIX = oldCondaPrefix; + } else { + delete process.env.CONDA_PREFIX; + } + if (oldCondaShlvl) { + process.env.CONDA_SHLVL = oldCondaShlvl; + } else { + delete process.env.CONDA_SHLVL; + } + sinon.restore(); + }); + + test('Shows prompt if base conda environment is activated and auto activate configuration is disabled', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelYes)) + .verifiable(TypeMoq.Times.once()); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base False' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + appShell.verifyAll(); + }); + + test('If user chooses yes, update interpreter path', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelYes)); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base False' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + pythonPathUpdaterService.verifyAll(); + }); + + test('If user chooses no, do not update interpreter path', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + pythonPathUpdaterService + .setup((p) => + p.updatePythonPath( + TypeMoq.It.isValue(condaPrefix), + TypeMoq.It.isValue(ConfigurationTarget.WorkspaceFolder), + TypeMoq.It.isValue('load'), + TypeMoq.It.isValue(uri), + ), + ) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelNo)); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base False' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + pythonPathUpdaterService.verifyAll(); + }); + + test('Do not show prompt if base conda environment is activated but auto activate configuration is enabled', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelYes)) + .verifiable(TypeMoq.Times.never()); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base True' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + appShell.verifyAll(); + }); + + test('Do not show prompt if non-base conda environment is activated', async () => { + process.env.CONDA_PREFIX = condaPrefix; + process.env.CONDA_SHLVL = '1'; + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'nonbase' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelYes)) + .verifiable(TypeMoq.Times.never()); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base False' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + appShell.verifyAll(); + }); + + test('Do not show prompt if conda environment is not activated', async () => { + interpreterService + .setup((i) => i.getInterpreterDetails(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ envName: 'base' } as unknown) as PythonEnvironment)); + workspaceService.setup((w) => w.workspaceFile).returns(() => undefined); + const workspaceFolder: WorkspaceFolder = { name: 'one', uri, index: 0 }; + workspaceService.setup((w) => w.workspaceFolders).returns(() => [workspaceFolder]); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), ...prompts)) + .returns(() => Promise.resolve(Common.bannerLabelYes)) + .verifiable(TypeMoq.Times.never()); + processService + .setup((p) => p.shellExec('conda config --get auto_activate_base')) + .returns(() => + Promise.resolve(({ stdout: '--set auto_activate_base False' } as unknown) as ExecutionResult< + string + >), + ); + activatedEnvLaunch = new ActivatedEnvironmentLaunch( + workspaceService.object, + appShell.object, + pythonPathUpdaterService.object, + interpreterService.object, + processServiceFactory.object, + ); + await activatedEnvLaunch._promptIfApplicable(); + appShell.verifyAll(); + }); + }); });