From 85e1185402c13e1c8af0444987ab6b8c794663f0 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Mon, 7 Nov 2022 12:04:38 -0800 Subject: [PATCH 1/7] Add msg to show deprecated support in the debugger --- .../debugger/extension/adapter/factory.ts | 41 ++++++++++++++++++- .../extension/adapter/factory.unit.test.ts | 36 ++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 1a84b73f2003..b3c9789d9065 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -21,13 +21,23 @@ import { EventName } from '../../../telemetry/constants'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; import * as nls from 'vscode-nls'; -import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; +import { showErrorMessage, showInformationMessage } from '../../../common/vscodeApis/windowApis'; +import { Common } from '../../../common/utils/localize'; +import { IPersistentStateFactory } from '../../../common/types'; const localize: nls.LocalizeFunc = nls.loadMessageBundle(); +// persistent state names, exported to make use of in testing +export enum debugStateKeys { + doNotShowAgain = 'doNotShowPython36DebugDeprecatedAgain', +} + @injectable() export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFactory { - constructor(@inject(IInterpreterService) private readonly interpreterService: IInterpreterService) {} + constructor( + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, + ) {} public async createDebugAdapterDescriptor( session: DebugSession, @@ -142,8 +152,35 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac return this.getExecutableCommand(interpreters[0]); } + private async showDeprecatedPythonMessage() { + const notificationPromptEnabled = this.persistentState.createGlobalPersistentState( + debugStateKeys.doNotShowAgain, + false, + ); + if (notificationPromptEnabled.value) { + return; + } + const prompts = [Common.doNotShowAgain]; + const selection = await showInformationMessage( + 'The debugger in the python extension no longer supports python versions minor than 3.7.', + ...prompts, + ); + if (!selection) { + return; + } + if (selection == Common.doNotShowAgain) { + // Never show the message again + await this.persistentState + .createGlobalPersistentState(debugStateKeys.doNotShowAgain, false) + .updateValue(true); + } + } + private async getExecutableCommand(interpreter: PythonEnvironment | undefined): Promise { if (interpreter) { + if ((interpreter.version?.major ?? 0) < 3 || (interpreter.version?.minor ?? 0) <= 6) { + this.showDeprecatedPythonMessage(); + } return interpreter.path.length > 0 ? [interpreter.path] : []; } return []; diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index 9686588b96d3..b82f6fb7d113 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -13,10 +13,10 @@ import { SemVer } from 'semver'; import { anything, instance, mock, verify, when } from 'ts-mockito'; import { DebugAdapterExecutable, DebugAdapterServer, DebugConfiguration, DebugSession, WorkspaceFolder } from 'vscode'; import { ConfigurationService } from '../../../../client/common/configuration/service'; -import { IPythonSettings } from '../../../../client/common/types'; +import { IPersistentStateFactory, IPythonSettings } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; -import { DebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/adapter/factory'; +import { DebugAdapterDescriptorFactory, debugStateKeys } from '../../../../client/debugger/extension/adapter/factory'; import { IDebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/types'; import { IInterpreterService } from '../../../../client/interpreter/contracts'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; @@ -24,13 +24,17 @@ import { EnvironmentType } from '../../../../client/pythonEnvironments/info'; import { clearTelemetryReporter } from '../../../../client/telemetry'; import { EventName } from '../../../../client/telemetry/constants'; import * as windowApis from '../../../../client/common/vscodeApis/windowApis'; +import { PersistentState, PersistentStateFactory } from '../../../../client/common/persistentState'; use(chaiAsPromised); suite('Debugging - Adapter Factory', () => { let factory: IDebugAdapterDescriptorFactory; let interpreterService: IInterpreterService; + let stateFactory: IPersistentStateFactory; + let state: PersistentState; let showErrorMessageStub: sinon.SinonStub; + let showInformationMessageStub: sinon.SinonStub; const nodeExecutable = undefined; const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy', 'adapter'); @@ -62,7 +66,15 @@ suite('Debugging - Adapter Factory', () => { process.env.VSC_PYTHON_CI_TEST = undefined; rewiremock.enable(); rewiremock('@vscode/extension-telemetry').with({ default: Reporter }); + stateFactory = mock(PersistentStateFactory); + state = mock(PersistentState) as PersistentState; + showErrorMessageStub = sinon.stub(windowApis, 'showErrorMessage'); + showInformationMessageStub = sinon.stub(windowApis, 'showInformationMessage'); + + when( + stateFactory.createGlobalPersistentState(debugStateKeys.doNotShowAgain, false), + ).thenReturn(instance(state)); const configurationService = mock(ConfigurationService); when(configurationService.getSettings(undefined)).thenReturn(({ @@ -74,7 +86,7 @@ suite('Debugging - Adapter Factory', () => { when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreter); when(interpreterService.getInterpreters(anything())).thenReturn([interpreter]); - factory = new DebugAdapterDescriptorFactory(instance(interpreterService)); + factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(stateFactory)); }); teardown(() => { @@ -138,7 +150,25 @@ suite('Debugging - Adapter Factory', () => { await expect(promise).to.eventually.be.rejectedWith('Debug Adapter Executable not provided'); sinon.assert.calledOnce(showErrorMessageStub); }); + test('Display a message if python version is less than 3.7', async () => { + when(interpreterService.getInterpreters(anything())).thenReturn([]); + const session = createSession({}); + const deprecatedInterpreter = { + architecture: Architecture.Unknown, + path: pythonPath, + sysPrefix: '', + sysVersion: '', + envType: EnvironmentType.Unknown, + version: new SemVer('3.6.12-test'), + }; + when(state.value).thenReturn(false); + + when(interpreterService.getActiveInterpreter(anything())).thenResolve(deprecatedInterpreter); + await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + sinon.assert.calledOnce(showInformationMessageStub); + }); test('Return Debug Adapter server if request is "attach", and port is specified directly', async () => { const session = createSession({ request: 'attach', port: 5678, host: 'localhost' }); const debugServer = new DebugAdapterServer(session.configuration.port, session.configuration.host); From eb120282f02ce8ef503563f04d53d74e916c508e Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Mon, 7 Nov 2022 12:05:32 -0800 Subject: [PATCH 2/7] Use a modal --- src/client/debugger/extension/adapter/factory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index b3c9789d9065..6c3fc7cf5b2f 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -163,6 +163,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac const prompts = [Common.doNotShowAgain]; const selection = await showInformationMessage( 'The debugger in the python extension no longer supports python versions minor than 3.7.', + { modal: true }, ...prompts, ); if (!selection) { From a0c9e1f35447aacedebdd8d671360141c5a7b904 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Mon, 7 Nov 2022 12:11:29 -0800 Subject: [PATCH 3/7] Use an error message instead of an information one --- src/client/debugger/extension/adapter/factory.ts | 4 ++-- src/test/debugger/extension/adapter/factory.unit.test.ts | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 6c3fc7cf5b2f..f64b74d9faac 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -21,7 +21,7 @@ import { EventName } from '../../../telemetry/constants'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; import * as nls from 'vscode-nls'; -import { showErrorMessage, showInformationMessage } from '../../../common/vscodeApis/windowApis'; +import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; import { Common } from '../../../common/utils/localize'; import { IPersistentStateFactory } from '../../../common/types'; @@ -161,7 +161,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac return; } const prompts = [Common.doNotShowAgain]; - const selection = await showInformationMessage( + const selection = await showErrorMessage( 'The debugger in the python extension no longer supports python versions minor than 3.7.', { modal: true }, ...prompts, diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index b82f6fb7d113..def169a362cd 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -34,7 +34,6 @@ suite('Debugging - Adapter Factory', () => { let stateFactory: IPersistentStateFactory; let state: PersistentState; let showErrorMessageStub: sinon.SinonStub; - let showInformationMessageStub: sinon.SinonStub; const nodeExecutable = undefined; const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy', 'adapter'); @@ -70,7 +69,6 @@ suite('Debugging - Adapter Factory', () => { state = mock(PersistentState) as PersistentState; showErrorMessageStub = sinon.stub(windowApis, 'showErrorMessage'); - showInformationMessageStub = sinon.stub(windowApis, 'showInformationMessage'); when( stateFactory.createGlobalPersistentState(debugStateKeys.doNotShowAgain, false), @@ -162,12 +160,11 @@ suite('Debugging - Adapter Factory', () => { version: new SemVer('3.6.12-test'), }; when(state.value).thenReturn(false); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(deprecatedInterpreter); await factory.createDebugAdapterDescriptor(session, nodeExecutable); - sinon.assert.calledOnce(showInformationMessageStub); + sinon.assert.calledOnce(showErrorMessageStub); }); test('Return Debug Adapter server if request is "attach", and port is specified directly', async () => { const session = createSession({ request: 'attach', port: 5678, host: 'localhost' }); From 0603cef374140a9b140bf715b726a166a643abb2 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Mon, 7 Nov 2022 12:16:57 -0800 Subject: [PATCH 4/7] Add localization --- src/client/debugger/extension/adapter/factory.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index f64b74d9faac..c3930c5704ca 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -162,7 +162,10 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac } const prompts = [Common.doNotShowAgain]; const selection = await showErrorMessage( - 'The debugger in the python extension no longer supports python versions minor than 3.7.', + localize( + 'deprecatedDebuggerError', + 'The debugger in the python extension no longer supports python versions minor than 3.7.', + ), { modal: true }, ...prompts, ); From f554489352ad7b8937d79a00c6fe0a66449abd1f Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Thu, 17 Nov 2022 10:12:34 -0800 Subject: [PATCH 5/7] Add change Interpreter Button --- src/client/common/utils/localize.ts | 1 + src/client/debugger/extension/adapter/factory.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 5d1eb52514c7..e78e7604455a 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -93,6 +93,7 @@ export namespace Common { export const alwaysIgnore = localize('Common.alwaysIgnore', 'Always Ignore'); export const ignore = localize('Common.ignore', 'Ignore'); export const selectPythonInterpreter = localize('Common.selectPythonInterpreter', 'Select Python Interpreter'); + export const changePythonInterpreter = localize('Common.changePythonInterpreter', 'Change Python Interpreter'); export const openLaunch = localize('Common.openLaunch', 'Open launch.json'); export const useCommandPrompt = localize('Common.useCommandPrompt', 'Use Command Prompt'); export const download = localize('Common.download', 'Download'); diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index c3930c5704ca..5eb63e84b9a1 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -24,6 +24,8 @@ import * as nls from 'vscode-nls'; import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; import { Common } from '../../../common/utils/localize'; import { IPersistentStateFactory } from '../../../common/types'; +import { Commands } from '../../../common/constants'; +import { ICommandManager } from '../../../common/application/types'; const localize: nls.LocalizeFunc = nls.loadMessageBundle(); @@ -35,6 +37,7 @@ export enum debugStateKeys { @injectable() export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFactory { constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, ) {} @@ -160,7 +163,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (notificationPromptEnabled.value) { return; } - const prompts = [Common.doNotShowAgain]; + const prompts = [Common.changePythonInterpreter, Common.doNotShowAgain]; const selection = await showErrorMessage( localize( 'deprecatedDebuggerError', @@ -172,6 +175,9 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (!selection) { return; } + if (selection == Common.changePythonInterpreter) { + await this.commandManager.executeCommand(Commands.Set_Interpreter); + } if (selection == Common.doNotShowAgain) { // Never show the message again await this.persistentState From f6269df69ac3ac1ecce6031b825f93a6e6f0098f Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Thu, 17 Nov 2022 10:15:51 -0800 Subject: [PATCH 6/7] Fix test --- .../debugger/extension/adapter/factory.unit.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index def169a362cd..6828ea96d451 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -25,6 +25,8 @@ import { clearTelemetryReporter } from '../../../../client/telemetry'; import { EventName } from '../../../../client/telemetry/constants'; import * as windowApis from '../../../../client/common/vscodeApis/windowApis'; import { PersistentState, PersistentStateFactory } from '../../../../client/common/persistentState'; +import { ICommandManager } from '../../../../client/common/application/types'; +import { CommandManager } from '../../../../client/common/application/commandManager'; use(chaiAsPromised); @@ -34,6 +36,7 @@ suite('Debugging - Adapter Factory', () => { let stateFactory: IPersistentStateFactory; let state: PersistentState; let showErrorMessageStub: sinon.SinonStub; + let commandManager: ICommandManager; const nodeExecutable = undefined; const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy', 'adapter'); @@ -67,6 +70,7 @@ suite('Debugging - Adapter Factory', () => { rewiremock('@vscode/extension-telemetry').with({ default: Reporter }); stateFactory = mock(PersistentStateFactory); state = mock(PersistentState) as PersistentState; + commandManager = mock(CommandManager); showErrorMessageStub = sinon.stub(windowApis, 'showErrorMessage'); @@ -84,7 +88,11 @@ suite('Debugging - Adapter Factory', () => { when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreter); when(interpreterService.getInterpreters(anything())).thenReturn([interpreter]); - factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(stateFactory)); + factory = new DebugAdapterDescriptorFactory( + instance(commandManager), + instance(interpreterService), + instance(stateFactory), + ); }); teardown(() => { From b1aadfab5d454bfcdb66b4a9609e7f02edc858c4 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Thu, 17 Nov 2022 13:54:15 -0800 Subject: [PATCH 7/7] Fix PR comments --- src/client/common/utils/localize.ts | 5 ++++- src/client/debugger/extension/adapter/factory.ts | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index e78e7604455a..1f15bdc77bf1 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -93,7 +93,6 @@ export namespace Common { export const alwaysIgnore = localize('Common.alwaysIgnore', 'Always Ignore'); export const ignore = localize('Common.ignore', 'Ignore'); export const selectPythonInterpreter = localize('Common.selectPythonInterpreter', 'Select Python Interpreter'); - export const changePythonInterpreter = localize('Common.changePythonInterpreter', 'Change Python Interpreter'); export const openLaunch = localize('Common.openLaunch', 'Open launch.json'); export const useCommandPrompt = localize('Common.useCommandPrompt', 'Use Command Prompt'); export const download = localize('Common.download', 'Download'); @@ -283,6 +282,10 @@ export namespace Interpreters { 'Interpreters.installPythonTerminalMessage', '💡 Please try installing the python package using your package manager. Alternatively you can also download it from https://www.python.org/downloads', ); + export const changePythonInterpreter = localize( + 'Interpreters.changePythonInterpreter', + 'Change Python Interpreter', + ); } export namespace InterpreterQuickPickList { diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 5eb63e84b9a1..10985fb97097 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -22,7 +22,7 @@ import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; import * as nls from 'vscode-nls'; import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; -import { Common } from '../../../common/utils/localize'; +import { Common, Interpreters } from '../../../common/utils/localize'; import { IPersistentStateFactory } from '../../../common/types'; import { Commands } from '../../../common/constants'; import { ICommandManager } from '../../../common/application/types'; @@ -163,10 +163,10 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (notificationPromptEnabled.value) { return; } - const prompts = [Common.changePythonInterpreter, Common.doNotShowAgain]; + const prompts = [Interpreters.changePythonInterpreter, Common.doNotShowAgain]; const selection = await showErrorMessage( localize( - 'deprecatedDebuggerError', + 'Debug.deprecatedDebuggerError', 'The debugger in the python extension no longer supports python versions minor than 3.7.', ), { modal: true }, @@ -175,7 +175,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (!selection) { return; } - if (selection == Common.changePythonInterpreter) { + if (selection == Interpreters.changePythonInterpreter) { await this.commandManager.executeCommand(Commands.Set_Interpreter); } if (selection == Common.doNotShowAgain) {