diff --git a/experiments.json b/experiments.json index 5133c4cb8a71..c6327eeba69d 100644 --- a/experiments.json +++ b/experiments.json @@ -32,26 +32,26 @@ { "name": "DebugAdapterFactory - control", "salt": "DebugAdapterFactory", - "min": 80, - "max": 90 + "min": 0, + "max": 0 }, { "name": "DebugAdapterFactory - experiment", "salt": "DebugAdapterFactory", - "min": 10, - "max": 20 + "min": 0, + "max": 0 }, { "name": "PtvsdWheels37 - control", "salt": "DebugAdapterFactory", - "min": 87, - "max": 90 + "min": 0, + "max": 0 }, { "name": "PtvsdWheels37 - experiment", "salt": "DebugAdapterFactory", - "min": 10, - "max": 13 + "min": 0, + "max": 0 }, { "name": "LS - enabled", diff --git a/news/2 Fixes/7836.md b/news/2 Fixes/7836.md new file mode 100644 index 000000000000..aea13d8ebb88 --- /dev/null +++ b/news/2 Fixes/7836.md @@ -0,0 +1 @@ +Do not use the PTVSD package version in the folder name for the wheel experiment. \ No newline at end of file diff --git a/package.nls.json b/package.nls.json index 1c70d070fa5c..dff2e611e8f3 100644 --- a/package.nls.json +++ b/package.nls.json @@ -121,7 +121,7 @@ "Experiments.inGroup": "User belongs to experiment group '{0}'", "Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters", "Interpreters.LoadingInterpreters": "Loading Python Interpreters", - "Interpreters.condaInheritEnvMessage": "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?", + "Interpreters.condaInheritEnvMessage": "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.", "Logging.CurrentWorkingDirectory": "cwd:", "Common.doNotShowAgain": "Do not show again", "Common.reload": "Reload", diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index 5521b4e8dd4d..2fbdb0379fc8 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -40,7 +40,10 @@ def install_ptvsd(): # Download only if it's a 3.7 wheel. if not wheel_info["python_version"].endswith(("37", "3.7")): continue - filename = wheel_info["filename"].rpartition(".")[0] # Trim the file extension. + + # Trim the file extension and remove the ptvsd version from the folder name. + filename = wheel_info["filename"].rpartition(".")[0] + filename = filename.replace(f"{version}-", "") ptvsd_path = path.join(PYTHONFILES, filename) with urllib.request.urlopen(wheel_info["url"]) as wheel_response: diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 8b8dce53c52a..2d6a56473939 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -6,11 +6,9 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PYTHONFILES = os.path.join(ROOT, "pythonFiles", "lib", "python") -REQUIREMENTS = os.path.join(ROOT, "requirements.txt") sys.path.insert(0, PYTHONFILES) -from packaging.requirements import Requirement from packaging.tags import sys_tags sys.path.remove(PYTHONFILES) @@ -19,23 +17,9 @@ def ptvsd_folder_name(): """Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter.""" - with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: - for line in reqsfile: - pkgreq = Requirement(line) - if pkgreq.name == "ptvsd": - specs = pkgreq.specifier - try: - spec, = specs - version = spec.version - except: - # Fallpack to use base PTVSD path. - print(PYTHONFILES, end="") - return - break - try: for tag in sys_tags(): - folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" + folder_name = f"ptvsd-{tag.interpreter}-{tag.abi}-{tag.platform}" folder_path = os.path.join(PYTHONFILES, folder_name) if os.path.exists(folder_path): print(folder_path, end="") diff --git a/pythonFiles/tests/__init__.py b/pythonFiles/tests/__init__.py index a400865e05bd..e2e6976acd68 100644 --- a/pythonFiles/tests/__init__.py +++ b/pythonFiles/tests/__init__.py @@ -10,4 +10,3 @@ DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, "debug_adapter") PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") -REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index e823d74d8e95..4bebd2eb5b48 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -15,55 +15,28 @@ from packaging.tags import sys_tags from ptvsd_folder_name import ptvsd_folder_name -from .. import PYTHONFILES, REQUIREMENTS - - -def open_requirements_with_ptvsd(): - return patch( - "ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\nptvsd==5.0.0") - ) - - -def open_requirements_without_ptvsd(): - return patch("ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n")) +from .. import PYTHONFILES class TestPtvsdFolderName: """Unit tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" - def test_requirement_exists_folder_exists(self, capsys): + def test_folder_exists(self, capsys): # Return the first constructed folder path as existing. patcher = patch("os.path.exists") mock_exists = patcher.start() mock_exists.side_effect = lambda p: True tag = next(sys_tags()) - folder = "ptvsd-5.0.0-{}-{}-{}".format(tag.interpreter, tag.abi, tag.platform) + folder = "ptvsd-{}-{}-{}".format(tag.interpreter, tag.abi, tag.platform) - with open_requirements_with_ptvsd(): - ptvsd_folder_name() + ptvsd_folder_name() patcher.stop() expected = os.path.join(PYTHONFILES, folder) captured = capsys.readouterr() assert captured.out == expected - def test_ptvsd_requirement_once(self): - reqs = [ - line - for line in open(REQUIREMENTS, "r", encoding="utf-8") - if re.match("ptvsd==", line) - ] - assert len(reqs) == 1 - - def test_no_ptvsd_requirement(self, capsys): - with open_requirements_without_ptvsd() as p: - ptvsd_folder_name() - - expected = PYTHONFILES - captured = capsys.readouterr() - assert captured.out == expected - def test_no_wheel_folder(self, capsys): # Return none of of the constructed paths as existing, # ptvsd_folder_name() should return the path to default ptvsd. @@ -71,8 +44,7 @@ def test_no_wheel_folder(self, capsys): mock_no_exist = patcher.start() mock_no_exist.side_effect = lambda p: False - with open_requirements_with_ptvsd() as p: - ptvsd_folder_name() + ptvsd_folder_name() patcher.stop() expected = PYTHONFILES diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py index 32231a74f744..db7512e84f51 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -13,22 +13,15 @@ import subprocess from packaging.requirements import Requirement -from .. import PYTHONFILES, REQUIREMENTS, SRC_ROOT +from .. import PYTHONFILES, SRC_ROOT ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] -PREFIX = "ptvsd==" - -with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: - for line in reqsfile: - if line.startswith(PREFIX): - VERSION = line[len(PREFIX) :].strip() - break def ptvsd_paths(*platforms): paths = set() for platform in platforms: - folder = "ptvsd-{}-cp37-cp37m-{}".format(VERSION, platform) + folder = "ptvsd-cp37-cp37m-{}".format(platform) paths.add(os.path.join(PYTHONFILES, folder)) return paths diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 883d86a28241..065636141a86 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -60,7 +60,7 @@ export namespace Experiments { export namespace Interpreters { export const loading = localize('Interpreters.LoadingInterpreters', 'Loading Python Interpreters'); export const refreshing = localize('Interpreters.RefreshingInterpreters', 'Refreshing Python Interpreters'); - export const condaInheritEnvMessage = localize('Interpreters.condaInheritEnvMessage', '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?'); + export const condaInheritEnvMessage = localize('Interpreters.condaInheritEnvMessage', '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 environmentPromptMessage = localize('Interpreters.environmentPromptMessage', 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?'); export const selectInterpreterTip = localize('Interpreters.selectInterpreterTip', 'Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar'); } diff --git a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts index 0dfec6eb5780..079154754bee 100644 --- a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts +++ b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts @@ -6,8 +6,9 @@ import { ConfigurationTarget, Uri } from 'vscode'; import { IExtensionActivationService } from '../../activation/types'; import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; import { traceDecorators, traceError } from '../../common/logger'; -import { IPersistentStateFactory } from '../../common/types'; -import { InteractiveShiftEnterBanner, Interpreters } from '../../common/utils/localize'; +import { IPlatformService } from '../../common/platform/types'; +import { IBrowserService, IPersistentStateFactory } from '../../common/types'; +import { Common, InteractiveShiftEnterBanner, Interpreters } from '../../common/utils/localize'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IInterpreterService, InterpreterType } from '../contracts'; @@ -21,8 +22,9 @@ export class CondaInheritEnvPrompt implements IExtensionActivationService { @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, + @inject(IPlatformService) private readonly platformService: IPlatformService, @optional() public hasPromptBeenShownInCurrentSession: boolean = false - ) { } + ) {} public async activate(resource: Uri): Promise { this.initializeInBackground(resource).ignoreErrors(); @@ -62,6 +64,9 @@ export class CondaInheritEnvPrompt implements IExtensionActivationService { if (this.hasPromptBeenShownInCurrentSession) { return false; } + if (this.platformService.isWindows) { + return false; + } const interpreter = await this.interpreterService.getActiveInterpreter(resource); if (!interpreter || interpreter.type !== InterpreterType.Conda) { return false; diff --git a/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts b/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts index 8939ee595c57..4cf3c85624eb 100644 --- a/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts +++ b/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts @@ -10,7 +10,8 @@ import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; import { PersistentStateFactory } from '../../../client/common/persistentState'; -import { IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { IPlatformService } from '../../../client/common/platform/types'; +import { IBrowserService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; import { createDeferred, createDeferredFromPromise, sleep } from '../../../client/common/utils/async'; import { InteractiveShiftEnterBanner, Interpreters } from '../../../client/common/utils/localize'; import { IInterpreterService, InterpreterType } from '../../../client/interpreter/contracts'; @@ -24,6 +25,8 @@ suite('Conda Inherit Env Prompt', async () => { let workspaceService: TypeMoq.IMock; let appShell: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; + let platformService: TypeMoq.IMock; + let browserService: TypeMoq.IMock; let persistentStateFactory: IPersistentStateFactory; let notificationPromptEnabled: TypeMoq.IMock>; let condaInheritEnvPrompt: CondaInheritEnvPrompt; @@ -39,10 +42,26 @@ suite('Conda Inherit Env Prompt', async () => { appShell = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); persistentStateFactory = mock(PersistentStateFactory); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + platformService = TypeMoq.Mock.ofType(); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); }); test('Returns false if prompt has already been shown in the current session', async () => { - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory), true); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object, + true + ); const workspaceConfig = TypeMoq.Mock.ofType(); interpreterService .setup(is => is.getActiveInterpreter(resource)) @@ -57,11 +76,25 @@ suite('Conda Inherit Env Prompt', async () => { expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(true, 'Should be true'); verifyAll(); }); + test('Returns false if on Windows', async () => { + platformService + .setup(ps => ps.isWindows) + .returns(() => true) + .verifiable(TypeMoq.Times.once()); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); + verifyAll(); + }); test('Returns false if active interpreter is not of type Conda', async () => { const interpreter = { type: InterpreterType.Pipenv }; const workspaceConfig = TypeMoq.Mock.ofType(); + platformService + .setup(ps => ps.isWindows) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); interpreterService .setup(is => is.getActiveInterpreter(resource)) .returns(() => Promise.resolve(interpreter) as any) @@ -77,6 +110,10 @@ suite('Conda Inherit Env Prompt', async () => { }); test('Returns false if no active interpreter is present', async () => { const workspaceConfig = TypeMoq.Mock.ofType(); + platformService + .setup(ps => ps.isWindows) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); interpreterService .setup(is => is.getActiveInterpreter(resource)) .returns(() => Promise.resolve(undefined)) @@ -95,6 +132,10 @@ suite('Conda Inherit Env Prompt', async () => { type: InterpreterType.Conda }; const workspaceConfig = TypeMoq.Mock.ofType(); + platformService + .setup(ps => ps.isWindows) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); interpreterService .setup(is => is.getActiveInterpreter(resource)) .returns(() => Promise.resolve(interpreter) as any) @@ -105,7 +146,8 @@ suite('Conda Inherit Env Prompt', async () => { .verifiable(TypeMoq.Times.once()); workspaceConfig .setup(ws => ws.inspect('integrated.inheritEnv')) - .returns(() => undefined); + .returns(() => undefined) + .verifiable(TypeMoq.Times.once()); const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); expect(result).to.equal(false, 'Prompt should not be shown'); expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); @@ -142,6 +184,10 @@ suite('Conda Inherit Env Prompt', async () => { type: InterpreterType.Conda }; const workspaceConfig = TypeMoq.Mock.ofType(); + platformService + .setup(ps => ps.isWindows) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); interpreterService .setup(is => is.getActiveInterpreter(resource)) .returns(() => Promise.resolve(interpreter) as any) @@ -169,6 +215,10 @@ suite('Conda Inherit Env Prompt', async () => { workspaceFolderValue: undefined }; const workspaceConfig = TypeMoq.Mock.ofType(); + platformService + .setup(ps => ps.isWindows) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); interpreterService .setup(is => is.getActiveInterpreter(resource)) .returns(() => Promise.resolve(interpreter) as any) @@ -193,6 +243,7 @@ suite('Conda Inherit Env Prompt', async () => { appShell = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); persistentStateFactory = mock(PersistentStateFactory); + platformService = TypeMoq.Mock.ofType(); }); teardown(() => { @@ -203,7 +254,14 @@ suite('Conda Inherit Env Prompt', async () => { const initializeInBackgroundDeferred = createDeferred(); initializeInBackground = sinon.stub(CondaInheritEnvPrompt.prototype, 'initializeInBackground'); initializeInBackground.callsFake(() => initializeInBackgroundDeferred.promise); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); const promise = condaInheritEnvPrompt.activate(resource); const deferred = createDeferredFromPromise(promise); @@ -220,7 +278,14 @@ suite('Conda Inherit Env Prompt', async () => { test('Ignores errors raised by initializeInBackground()', async () => { initializeInBackground = sinon.stub(CondaInheritEnvPrompt.prototype, 'initializeInBackground'); initializeInBackground.rejects(new Error('Kaboom')); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); await condaInheritEnvPrompt.activate(resource); assert.ok(initializeInBackground.calledOnce); }); @@ -234,6 +299,7 @@ suite('Conda Inherit Env Prompt', async () => { appShell = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); persistentStateFactory = mock(PersistentStateFactory); + platformService = TypeMoq.Mock.ofType(); }); teardown(() => { @@ -245,7 +311,14 @@ suite('Conda Inherit Env Prompt', async () => { shouldShowPrompt.callsFake(() => Promise.resolve(true)); promptAndUpdate = sinon.stub(CondaInheritEnvPrompt.prototype, 'promptAndUpdate'); promptAndUpdate.callsFake(() => Promise.resolve(undefined)); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); await condaInheritEnvPrompt.initializeInBackground(resource); assert.ok(shouldShowPrompt.calledOnce); assert.ok(promptAndUpdate.calledOnce); @@ -256,7 +329,14 @@ suite('Conda Inherit Env Prompt', async () => { shouldShowPrompt.callsFake(() => Promise.resolve(false)); promptAndUpdate = sinon.stub(CondaInheritEnvPrompt.prototype, 'promptAndUpdate'); promptAndUpdate.callsFake(() => Promise.resolve(undefined)); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); await condaInheritEnvPrompt.initializeInBackground(resource); assert.ok(shouldShowPrompt.calledOnce); assert.ok(promptAndUpdate.notCalled); @@ -271,8 +351,16 @@ suite('Conda Inherit Env Prompt', async () => { interpreterService = TypeMoq.Mock.ofType(); persistentStateFactory = mock(PersistentStateFactory); notificationPromptEnabled = TypeMoq.Mock.ofType>(); + platformService = TypeMoq.Mock.ofType(); when(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).thenReturn(notificationPromptEnabled.object); - condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt( + interpreterService.object, + workspaceService.object, + browserService.object, + appShell.object, + instance(persistentStateFactory), + platformService.object + ); }); test('Does not display prompt if it is disabled', async () => {