From 6870b41abef7424a791af413d5045bd514b46a9b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 29 Aug 2019 16:22:11 -0700 Subject: [PATCH 01/45] Get PR validation going, fix unit tests later --- pythonFiles/ptvsd_folder_name.py | 38 +++++++++++++++++++ .../debugger/extension/adapter/factory.ts | 36 +++++++++++++----- .../debugger/extension/serviceRegistry.ts | 27 ++----------- src/client/debugger/extension/types.ts | 6 --- .../extension/adapter/factory.unit.test.ts | 6 ++- .../extension/serviceRegistry.unit.test.ts | 6 +-- 6 files changed, 76 insertions(+), 43 deletions(-) create mode 100644 pythonFiles/ptvsd_folder_name.py diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py new file mode 100644 index 000000000000..90a01c21e852 --- /dev/null +++ b/pythonFiles/ptvsd_folder_name.py @@ -0,0 +1,38 @@ +import sys +from os import path + +ROOT_DIRNAME = path.dirname(path.dirname(path.abspath(__file__))) +PYTHONFILES_PATH = path.join(ROOT_DIRNAME, "pythonFiles", "lib", "python") +REQUIREMENTS_PATH = path.join(ROOT_DIRNAME, "requirements.txt") + +sys.path.insert(0, PYTHONFILES_PATH) + +from packaging.tags import sys_tags +from packaging.requirements import Requirement + + +def ptvsd_folder_name(): + """Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter.""" + + with open(REQUIREMENTS_PATH, "r", encoding="utf-8") as requirements: + for line in requirements: + package_requirement = Requirement(line) + if package_requirement.name != "ptvsd": + continue + requirement_specifier = package_requirement.specifier + ptvsd_version = next(requirement_specifier.__iter__()).version + + sys.path.remove(PYTHONFILES_PATH) + + for tag in sys_tags(): + folder_name = ( + f"ptvsd-{ptvsd_version}-{tag.interpreter}-{tag.abi}-{tag.platform}" + ) + folder_path = path.join(PYTHONFILES_PATH, folder_name) + if path.exists(folder_path): + print(f"{folder_path}") + return + + +if __name__ == "__main__": + ptvsd_folder_name() diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 73ee16730df5..fa0630c0cb0d 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -4,11 +4,14 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { DebugAdapterDescriptor, DebugAdapterExecutable, DebugSession, WorkspaceFolder } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; import { DebugAdapterNewPtvsd } from '../../../common/experimentGroups'; import { traceVerbose } from '../../../common/logger'; +import { ExecutionResult, IPythonExecutionFactory } from '../../../common/process/types'; import { IExperimentsManager } from '../../../common/types'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; @@ -18,7 +21,8 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager + @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, + @inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory ) {} public async createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable | undefined): Promise { const configuration = session.configuration as (LaunchRequestArguments | AttachRequestArguments); @@ -26,16 +30,15 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); if (interpreterInfo && interpreterInfo.version && interpreterInfo.version.raw.startsWith('3.7') && this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { - traceVerbose('Compute and return the path to the correct PTVSD folder (use packaging module)'); - // const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'ptvsd'); - // // tslint:disable-next-line: no-any - // const ptvsdPathToUse = 'ptvsd' in configuration ? (configuration as any).ptvsd : ptvsdPath; - // traceVerbose(`Using Python Debug Adapter with PTVSD ${ptvsdPathToUse}`); - // return new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPathToUse, 'adapter'), ...logArgs]); - return new DebugAdapterExecutable(pythonPath); + const ptvsdPathToUse = await this.getPtvsdFolder(pythonPath).then(output => output.stdout.trim()); + // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. + const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; + + return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'adapter'), ...logArgs]); } + + // Use the Node debug adapter (and ptvsd_launcher.py) if (executable) { - traceVerbose('Using Node Debug Adapter'); return executable; } // Unlikely scenario. @@ -84,4 +87,19 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac // tslint:disable-next-line: messages-must-be-localized await this.appShell.showErrorMessage('Please install Python or select a Python Interpereter to use the debugger.'); } + + /** + * Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter. + * + * @private + * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) + * @returns {Promise>} + * @memberof DebugAdapterDescriptorFactory + */ + private async getPtvsdFolder(pythonPath: string): Promise> { + const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); + + const pythonProcess = await this.executionFactory.create({ pythonPath }); + return pythonProcess.exec([pathToScript], {}); + } } diff --git a/src/client/debugger/extension/serviceRegistry.ts b/src/client/debugger/extension/serviceRegistry.ts index 3e9cbe108bff..b3facb549d05 100644 --- a/src/client/debugger/extension/serviceRegistry.ts +++ b/src/client/debugger/extension/serviceRegistry.ts @@ -26,26 +26,11 @@ import { IDebugConfigurationProviderFactory, IDebugConfigurationResolver } from import { ChildProcessAttachEventHandler } from './hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from './hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './hooks/types'; -import { - DebugConfigurationType, - ExtensionSingleActivationServiceType, - IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, - IDebugConfigurationService, - IDebuggerBanner -} from './types'; +import { DebugConfigurationType, IDebugAdapterDescriptorFactory, IDebugConfigurationProvider, IDebugConfigurationService, IDebuggerBanner } from './types'; export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonCompletionProvider, - ExtensionSingleActivationServiceType.jsonCompletionProvider - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonUpdaterService, - ExtensionSingleActivationServiceType.jsonUpdaterService - ); + serviceManager.addSingleton(IExtensionSingleActivationService, LaunchJsonCompletionProvider); + serviceManager.addSingleton(IExtensionSingleActivationService, LaunchJsonUpdaterService); serviceManager.addSingleton(IDebugConfigurationService, PythonDebugConfigurationService); serviceManager.addSingleton(IDebuggerBanner, DebuggerBanner); serviceManager.addSingleton(IChildProcessAttachService, ChildProcessAttachService); @@ -60,10 +45,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IDebugConfigurationProvider, ModuleLaunchDebugConfigurationProvider, DebugConfigurationType.launchModule); serviceManager.addSingleton(IDebugConfigurationProvider, PyramidLaunchDebugConfigurationProvider, DebugConfigurationType.launchPyramid); serviceManager.addSingleton(IDebugEnvironmentVariablesService, DebugEnvironmentVariablesHelper); - serviceManager.addSingleton( - IExtensionSingleActivationService, - DebugAdapterActivator, - ExtensionSingleActivationServiceType.debugAdapterActivator - ); + serviceManager.addSingleton(IExtensionSingleActivationService, DebugAdapterActivator); serviceManager.addSingleton(IDebugAdapterDescriptorFactory, DebugAdapterDescriptorFactory); } diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index 7f02db408e77..04b862b85b36 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -35,11 +35,5 @@ export enum PythonPathSource { settingsJson = 'settings.json' } -export enum ExtensionSingleActivationServiceType { - jsonCompletionProvider = 'jsonCompletionProvider', - jsonUpdaterService = 'jsonUpdaterService', - debugAdapterActivator = 'debugAdapterActivator' -} - export const IDebugAdapterDescriptorFactory = Symbol('IDebugAdapterDescriptorFactory'); export interface IDebugAdapterDescriptorFactory extends DebugAdapterDescriptorFactory {} diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index a45789d5b7b7..65cc36d1981e 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -14,6 +14,8 @@ import { ApplicationShell } from '../../../../client/common/application/applicat import { IApplicationShell } from '../../../../client/common/application/types'; import { DebugAdapterNewPtvsd } from '../../../../client/common/experimentGroups'; import { ExperimentsManager } from '../../../../client/common/experiments'; +import { PythonExecutionFactory } from '../../../../client/common/process/pythonExecutionFactory'; +import { IPythonExecutionFactory } from '../../../../client/common/process/types'; import { IExperimentsManager } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; import { DebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/adapter/factory'; @@ -28,6 +30,7 @@ suite('Debugging - Adapter Factory', () => { let interpreterService: IInterpreterService; let appShell: IApplicationShell; let experimentsManager: IExperimentsManager; + let executionFactory: IPythonExecutionFactory; const nodeExecutable = { command: 'node', args: [] }; setup(() => { @@ -43,7 +46,8 @@ suite('Debugging - Adapter Factory', () => { appShell = mock(ApplicationShell); appShell = mock(ApplicationShell); experimentsManager = mock(ExperimentsManager); - factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(appShell), instance(experimentsManager)); + executionFactory = mock(PythonExecutionFactory); + factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(appShell), instance(experimentsManager), instance(executionFactory)); }); function createSession(config: Partial, workspaceFolder?: WorkspaceFolder): DebugSession { diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index 82b417494430..45a5778c2137 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -30,7 +30,6 @@ import { IChildProcessAttachService, IDebugSessionEventHandlers } from '../../.. import { registerTypes } from '../../../client/debugger/extension/serviceRegistry'; import { DebugConfigurationType, - ExtensionSingleActivationServiceType, IDebugAdapterDescriptorFactory, IDebugConfigurationProvider, IDebugConfigurationService, @@ -46,9 +45,6 @@ suite('Debugging - Service Registry', () => { [IDebugConfigurationService, PythonDebugConfigurationService], [IDebuggerBanner, DebuggerBanner], [IChildProcessAttachService, ChildProcessAttachService], - [IExtensionSingleActivationService, LaunchJsonCompletionProvider, ExtensionSingleActivationServiceType.jsonCompletionProvider], - [IExtensionSingleActivationService, LaunchJsonUpdaterService, ExtensionSingleActivationServiceType.jsonUpdaterService], - [IExtensionSingleActivationService, DebugAdapterActivator, ExtensionSingleActivationServiceType.debugAdapterActivator], [IDebugAdapterDescriptorFactory, DebugAdapterDescriptorFactory], [IDebugSessionEventHandlers, ChildProcessAttachEventHandler], [IDebugConfigurationResolver, LaunchConfigurationResolver, 'launch'], @@ -74,6 +70,8 @@ suite('Debugging - Service Registry', () => { } }); + // IExtensionSingleActivationService is a special case + registerTypes(serviceManager.object); serviceManager.verifyAll(); }); From 6e6cac1224cdfcde12e3120a0279910b5f034da8 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 30 Aug 2019 08:02:27 -0700 Subject: [PATCH 02/45] Don't flatten the folder structure too much --- pythonFiles/install_ptvsd.py | 4 ++-- src/client/debugger/extension/adapter/factory.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index 7065293f6018..c53e670bc380 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -40,8 +40,8 @@ def install_ptvsd(): with urllib.request.urlopen(wheel_info["url"]) as wheel_response: wheel_file = BytesIO(wheel_response.read()) - # Extract only the contents of the ptvsd subfolder. - prefix = path.join(f"ptvsd-{ptvsd_version}.data", "purelib", "ptvsd") + # Extract only the contents of the purelib subfolder (parent folder of ptvsd). + prefix = path.join(f"ptvsd-{ptvsd_version}.data", "purelib") with ZipFile(wheel_file, "r") as wheel: for zip_info in wheel.infolist(): diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index fa0630c0cb0d..d0815d34c393 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -34,7 +34,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; - return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'adapter'), ...logArgs]); + return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'ptvsd', 'adapter'), ...logArgs]); } // Use the Node debug adapter (and ptvsd_launcher.py) From bc3a3a7a82296d21c36efa70c255e7b9b2afaa2e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 30 Aug 2019 08:04:37 -0700 Subject: [PATCH 03/45] Longer comment --- pythonFiles/install_ptvsd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index c53e670bc380..b42e1b5ab1d4 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -40,7 +40,8 @@ def install_ptvsd(): with urllib.request.urlopen(wheel_info["url"]) as wheel_response: wheel_file = BytesIO(wheel_response.read()) - # Extract only the contents of the purelib subfolder (parent folder of ptvsd). + # Extract only the contents of the purelib subfolder (parent folder of ptvsd), + # since ptvsd files rely on the presence of a 'ptvsd' folder. prefix = path.join(f"ptvsd-{ptvsd_version}.data", "purelib") with ZipFile(wheel_file, "r") as wheel: From 22909dffa1403b05ae8e432e2e84185d658af734 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 30 Aug 2019 08:48:14 -0700 Subject: [PATCH 04/45] Fix service registry unit tests --- src/test/debugger/extension/serviceRegistry.unit.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index 45a5778c2137..9d19f5377aaf 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -46,6 +46,9 @@ suite('Debugging - Service Registry', () => { [IDebuggerBanner, DebuggerBanner], [IChildProcessAttachService, ChildProcessAttachService], [IDebugAdapterDescriptorFactory, DebugAdapterDescriptorFactory], + [IExtensionSingleActivationService, LaunchJsonCompletionProvider], + [IExtensionSingleActivationService, LaunchJsonUpdaterService], + [IExtensionSingleActivationService, DebugAdapterActivator], [IDebugSessionEventHandlers, ChildProcessAttachEventHandler], [IDebugConfigurationResolver, LaunchConfigurationResolver, 'launch'], [IDebugConfigurationResolver, AttachConfigurationResolver, 'attach'], @@ -59,19 +62,17 @@ suite('Debugging - Service Registry', () => { ].forEach(mapping => { if (mapping.length === 2) { serviceManager - .setup(s => s.addSingleton(typemoq.It.isValue(mapping[0] as any), typemoq.It.isAny())) + .setup(s => s.addSingleton(mapping[0] as any, mapping[1] as any)) .callback((_, cls) => expect(cls).to.equal(mapping[1])) .verifiable(typemoq.Times.once()); } else { serviceManager - .setup(s => s.addSingleton(typemoq.It.isValue(mapping[0] as any), typemoq.It.isAny(), typemoq.It.isValue(mapping[2] as any))) + .setup(s => s.addSingleton(mapping[0] as any, mapping[1] as any, mapping[2] as any)) .callback((_, cls) => expect(cls).to.equal(mapping[1])) .verifiable(typemoq.Times.once()); } }); - // IExtensionSingleActivationService is a special case - registerTypes(serviceManager.object); serviceManager.verifyAll(); }); From 0e063073cbef70c1b13cb4d2614bf0bc00c2066f Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 4 Sep 2019 14:21:27 -0700 Subject: [PATCH 05/45] Add fallback to base PTVSD path --- pythonFiles/ptvsd_folder_name.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 90a01c21e852..8b7458a02f2f 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -30,9 +30,12 @@ def ptvsd_folder_name(): ) folder_path = path.join(PYTHONFILES_PATH, folder_name) if path.exists(folder_path): - print(f"{folder_path}") + print(folder_path) return + # Fallback to use base PTVSD path. + print(PYTHONFILES_PATH) + if __name__ == "__main__": ptvsd_folder_name() From 002220cc87158edd113bebc72ff5c4cfcd06f60a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 4 Sep 2019 14:22:15 -0700 Subject: [PATCH 06/45] Update ptvsd folder retrieval (cache path) --- .../debugger/extension/adapter/factory.ts | 46 ++++++++++++++----- src/client/debugger/extension/types.ts | 2 + .../extension/adapter/factory.unit.test.ts | 18 +++++++- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index d0815d34c393..cf364471f9cf 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -5,16 +5,20 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { parse } from 'semver'; import { DebugAdapterDescriptor, DebugAdapterExecutable, DebugSession, WorkspaceFolder } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; +import { PVSC_EXTENSION_ID } from '../../../common/constants'; import { DebugAdapterNewPtvsd } from '../../../common/experimentGroups'; import { traceVerbose } from '../../../common/logger'; -import { ExecutionResult, IPythonExecutionFactory } from '../../../common/process/types'; -import { IExperimentsManager } from '../../../common/types'; +import { IPythonExecutionFactory } from '../../../common/process/types'; +import { IExperimentsManager, IExtensions, IPersistentStateFactory } from '../../../common/types'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; -import { IDebugAdapterDescriptorFactory } from '../types'; +import { DebugAdapterPtvsdPathInfo, IDebugAdapterDescriptorFactory } from '../types'; + +export const ptvsdPathStorageKey = 'PTVSD_PATH_STORAGE_KEY'; @injectable() export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFactory { @@ -22,17 +26,19 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, - @inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory + @inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory, + @inject(IPersistentStateFactory) private readonly stateFactory: IPersistentStateFactory, + @inject(IExtensions) private readonly extensions: IExtensions ) {} public async createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable | undefined): Promise { const configuration = session.configuration as (LaunchRequestArguments | AttachRequestArguments); const pythonPath = await this.getPythonPath(configuration, session.workspaceFolder); const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); - if (interpreterInfo && interpreterInfo.version && interpreterInfo.version.raw.startsWith('3.7') && this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { - const ptvsdPathToUse = await this.getPtvsdFolder(pythonPath).then(output => output.stdout.trim()); + if (this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment) && interpreterInfo && interpreterInfo.version && interpreterInfo.version.raw.startsWith('3.7')) { // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; + const ptvsdPathToUse = await this.getPtvsdFolder(pythonPath); return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'ptvsd', 'adapter'), ...logArgs]); } @@ -90,16 +96,34 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac /** * Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter. + * Use `ptvsd_folder_name.py` to compute the experimental PTVSD folder name in 2 cases: + * - It has never been computed before; + * - The extension number has changed since the last time it was cached. + * + * Return a cached path otherwise. * * @private * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) - * @returns {Promise>} + * @returns {Promise} * @memberof DebugAdapterDescriptorFactory */ - private async getPtvsdFolder(pythonPath: string): Promise> { - const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); + private async getPtvsdFolder(pythonPath: string): Promise { + const persistentState = this.stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined); + let pathToPtvsd = ''; + + const extension = this.extensions.getExtension(PVSC_EXTENSION_ID)!; + const version = parse(extension.packageJSON.version)!; + + if (!persistentState.value || version.raw !== persistentState.value.extensionVersion) { + const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); + const pythonProcess = await this.executionFactory.create({ pythonPath }); + + pathToPtvsd = await pythonProcess.exec([pathToScript], {}).then(output => output.stdout.trim()); + await persistentState.updateValue({ extensionVersion: version.raw, ptvsdPath: pathToPtvsd }); + } else { + pathToPtvsd = persistentState.value.ptvsdPath; + } - const pythonProcess = await this.executionFactory.create({ pythonPath }); - return pythonProcess.exec([pathToScript], {}); + return new Promise(resolve => resolve(pathToPtvsd)); } } diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index 04b862b85b36..f5fd4ae5d893 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -37,3 +37,5 @@ export enum PythonPathSource { export const IDebugAdapterDescriptorFactory = Symbol('IDebugAdapterDescriptorFactory'); export interface IDebugAdapterDescriptorFactory extends DebugAdapterDescriptorFactory {} + +export type DebugAdapterPtvsdPathInfo = { extensionVersion: string; ptvsdPath: string }; diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index 65cc36d1981e..da03c3265dfb 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -11,17 +11,20 @@ import { SemVer } from 'semver'; import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import { DebugAdapterExecutable, DebugConfiguration, DebugSession, WorkspaceFolder } from 'vscode'; import { ApplicationShell } from '../../../../client/common/application/applicationShell'; +import { Extensions } from '../../../../client/common/application/extensions'; import { IApplicationShell } from '../../../../client/common/application/types'; import { DebugAdapterNewPtvsd } from '../../../../client/common/experimentGroups'; import { ExperimentsManager } from '../../../../client/common/experiments'; +import { PersistentStateFactory } from '../../../../client/common/persistentState'; import { PythonExecutionFactory } from '../../../../client/common/process/pythonExecutionFactory'; import { IPythonExecutionFactory } from '../../../../client/common/process/types'; -import { IExperimentsManager } from '../../../../client/common/types'; +import { IExperimentsManager, IExtensions, IPersistentStateFactory } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; import { DebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/adapter/factory'; import { IDebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/types'; import { IInterpreterService, InterpreterType } from '../../../../client/interpreter/contracts'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; + use(chaiAsPromised); // tslint:disable-next-line: max-func-body-length @@ -31,6 +34,8 @@ suite('Debugging - Adapter Factory', () => { let appShell: IApplicationShell; let experimentsManager: IExperimentsManager; let executionFactory: IPythonExecutionFactory; + let stateFactory: IPersistentStateFactory; + let extensions: IExtensions; const nodeExecutable = { command: 'node', args: [] }; setup(() => { @@ -47,7 +52,16 @@ suite('Debugging - Adapter Factory', () => { appShell = mock(ApplicationShell); experimentsManager = mock(ExperimentsManager); executionFactory = mock(PythonExecutionFactory); - factory = new DebugAdapterDescriptorFactory(instance(interpreterService), instance(appShell), instance(experimentsManager), instance(executionFactory)); + stateFactory = mock(PersistentStateFactory); + extensions = mock(Extensions); + factory = new DebugAdapterDescriptorFactory( + instance(interpreterService), + instance(appShell), + instance(experimentsManager), + instance(executionFactory), + instance(stateFactory), + instance(extensions) + ); }); function createSession(config: Partial, workspaceFolder?: WorkspaceFolder): DebugSession { From 44d3372b22edfe5c1f96bbac53dadcaedcf0941e Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 4 Sep 2019 15:21:26 -0700 Subject: [PATCH 07/45] Fix existing tests --- .../extension/adapter/factory.unit.test.ts | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index da03c3265dfb..ac781e22e9f3 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -15,18 +15,20 @@ import { Extensions } from '../../../../client/common/application/extensions'; import { IApplicationShell } from '../../../../client/common/application/types'; import { DebugAdapterNewPtvsd } from '../../../../client/common/experimentGroups'; import { ExperimentsManager } from '../../../../client/common/experiments'; -import { PersistentStateFactory } from '../../../../client/common/persistentState'; +import { PersistentState, PersistentStateFactory } from '../../../../client/common/persistentState'; import { PythonExecutionFactory } from '../../../../client/common/process/pythonExecutionFactory'; -import { IPythonExecutionFactory } from '../../../../client/common/process/types'; -import { IExperimentsManager, IExtensions, IPersistentStateFactory } from '../../../../client/common/types'; +import { PythonExecutionService } from '../../../../client/common/process/pythonProcess'; +import { IPythonExecutionFactory, IPythonExecutionService } from '../../../../client/common/process/types'; +import { IExperimentsManager, IExtensions, IPersistentState, IPersistentStateFactory } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; -import { DebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/adapter/factory'; -import { IDebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/types'; +import { DebugAdapterDescriptorFactory, ptvsdPathStorageKey } from '../../../../client/debugger/extension/adapter/factory'; +import { DebugAdapterPtvsdPathInfo, IDebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/types'; import { IInterpreterService, InterpreterType } from '../../../../client/interpreter/contracts'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; use(chaiAsPromised); +// tslint:disable: no-any // tslint:disable-next-line: max-func-body-length suite('Debugging - Adapter Factory', () => { let factory: IDebugAdapterDescriptorFactory; @@ -34,12 +36,24 @@ suite('Debugging - Adapter Factory', () => { let appShell: IApplicationShell; let experimentsManager: IExperimentsManager; let executionFactory: IPythonExecutionFactory; + let pythonProcess: IPythonExecutionService; let stateFactory: IPersistentStateFactory; + let debugAdapterPersistentState: IPersistentState; let extensions: IExtensions; const nodeExecutable = { command: 'node', args: [] }; + const mockExtensionVersion = new SemVer('2019.9.0'); setup(() => { interpreterService = mock(InterpreterService); + appShell = mock(ApplicationShell); + appShell = mock(ApplicationShell); + experimentsManager = mock(ExperimentsManager); + executionFactory = mock(PythonExecutionFactory); + pythonProcess = mock(PythonExecutionService); + stateFactory = mock(PersistentStateFactory); + debugAdapterPersistentState = mock(PersistentState); + extensions = mock(Extensions); + const interpreter = { architecture: Architecture.Unknown, path: path.join('path', 'to', 'active', 'interpreter'), @@ -47,13 +61,11 @@ suite('Debugging - Adapter Factory', () => { sysVersion: '', type: InterpreterType.Unknown }; + when(interpreterService.getInterpreters(anything())).thenResolve([interpreter]); - appShell = mock(ApplicationShell); - appShell = mock(ApplicationShell); - experimentsManager = mock(ExperimentsManager); - executionFactory = mock(PythonExecutionFactory); - stateFactory = mock(PersistentStateFactory); - extensions = mock(Extensions); + when(executionFactory.create(anything())).thenResolve(instance(pythonProcess)); + when(extensions.getExtension(anything())).thenReturn({ packageJSON: { version: mockExtensionVersion } } as any); + factory = new DebugAdapterDescriptorFactory( instance(interpreterService), instance(appShell), @@ -64,6 +76,13 @@ suite('Debugging - Adapter Factory', () => { ); }); + function mockPtvsdInfoPersistentState(sameVersion: boolean, ptvsdPath: string) { + const debugAdapterInfo: DebugAdapterPtvsdPathInfo = { extensionVersion: sameVersion ? mockExtensionVersion.raw : '2019.10.0-dev', ptvsdPath }; + + when(stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined)).thenReturn(instance(debugAdapterPersistentState)); + when(debugAdapterPersistentState.value).thenReturn(debugAdapterInfo); + } + function createSession(config: Partial, workspaceFolder?: WorkspaceFolder): DebugSession { return { configuration: { name: '', request: 'launch', type: 'python', ...config }, @@ -76,6 +95,7 @@ suite('Debugging - Adapter Factory', () => { } test('Return the value of configuration.pythonPath as the current python path if it exists and if we are in the experiment', async () => { + const ptvsdPath = path.join('path', 'to', 'ptvsd'); const pythonPath = path.join('session', 'path', 'to', 'python'); const session = createSession({ pythonPath }); const interpreterDetails = { @@ -86,8 +106,9 @@ suite('Debugging - Adapter Factory', () => { type: InterpreterType.Unknown, version: new SemVer('3.7.4-test') }; - const debugExecutable = new DebugAdapterExecutable(pythonPath); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + mockPtvsdInfoPersistentState(true, ptvsdPath); when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -97,6 +118,7 @@ suite('Debugging - Adapter Factory', () => { }); test('Return the path of the active interpreter as the current python path if we are in the experiment, it exists and configuration.pythonPath is not defined', async () => { + const ptvsdPath = path.join('path', 'to', 'ptvsd'); const pythonPath = path.join('active', 'python', 'interpreter'); const session = createSession({}); const interpreterDetails = { @@ -107,8 +129,9 @@ suite('Debugging - Adapter Factory', () => { type: InterpreterType.Unknown, version: new SemVer('3.7.4-test') }; - const debugExecutable = new DebugAdapterExecutable(pythonPath); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + mockPtvsdInfoPersistentState(true, ptvsdPath); when(interpreterService.getActiveInterpreter(anything())).thenResolve(interpreterDetails); when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -119,6 +142,7 @@ suite('Debugging - Adapter Factory', () => { }); test('Return the path of the first available interpreter as the current python path if we are in the experiment, configuration.pythonPath is not defined and there is no active interpreter', async () => { + const ptvsdPath = path.join('path', 'to', 'ptvsd'); const pythonPath = path.join('first', 'available', 'interpreter'); const session = createSession({}); const interpreterDetails = { @@ -129,8 +153,9 @@ suite('Debugging - Adapter Factory', () => { type: InterpreterType.Unknown, version: new SemVer('3.7.4-test') }; - const debugExecutable = new DebugAdapterExecutable(pythonPath); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + mockPtvsdInfoPersistentState(true, ptvsdPath); when(interpreterService.getInterpreters(anything())).thenResolve([interpreterDetails]); when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -175,8 +200,8 @@ suite('Debugging - Adapter Factory', () => { assert.deepEqual(descriptor, nodeExecutable); }); - test('Return debug adapter executable when in the experiment and with the active interpreter being Python 3.7', async () => { - // will be updated when we support the new debug adapter + test('Return Python debug adapter executable when in the experiment and with the active interpreter being Python 3.7', async () => { + const ptvsdPath = path.join('path', 'to', 'ptvsd'); const pythonPath = path.join('path', 'to', 'active', 'interpreter'); const interpreterDetails = { architecture: Architecture.Unknown, @@ -186,9 +211,10 @@ suite('Debugging - Adapter Factory', () => { type: InterpreterType.Unknown, version: { raw: '3.7.4', major: 3, minor: 7, build: ['foo'], patch: 0, prerelease: ['bar'] } }; - const debugExecutable = new DebugAdapterExecutable(pythonPath); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); const session = createSession({}); + mockPtvsdInfoPersistentState(true, ptvsdPath); when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); @@ -197,7 +223,7 @@ suite('Debugging - Adapter Factory', () => { assert.deepEqual(descriptor, debugExecutable); }); - test('Throw an error if the executable has not been defined', async () => { + test('Throw an error if the Node debugger adapter executable has not been defined', async () => { const session = createSession({}); const promise = factory.createDebugAdapterDescriptor(session, undefined); From 577ec911f4d2c4b0b2ef47a1384933bf16bf2a49 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 4 Sep 2019 15:51:51 -0700 Subject: [PATCH 08/45] Mock things in setup() function when possible --- .../extension/adapter/factory.unit.test.ts | 88 +++++-------------- 1 file changed, 23 insertions(+), 65 deletions(-) diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index ac781e22e9f3..1cda6eccef6c 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -38,15 +38,25 @@ suite('Debugging - Adapter Factory', () => { let executionFactory: IPythonExecutionFactory; let pythonProcess: IPythonExecutionService; let stateFactory: IPersistentStateFactory; - let debugAdapterPersistentState: IPersistentState; + let debugAdapterPersistentState: IPersistentState; let extensions: IExtensions; + const nodeExecutable = { command: 'node', args: [] }; const mockExtensionVersion = new SemVer('2019.9.0'); + const ptvsdPath = path.join('path', 'to', 'ptvsd'); + const pythonPath = path.join('path', 'to', 'python', 'interpreter'); + const interpreter = { + architecture: Architecture.Unknown, + path: pythonPath, + sysPrefix: '', + sysVersion: '', + type: InterpreterType.Unknown, + version: new SemVer('3.7.4-test') + }; setup(() => { interpreterService = mock(InterpreterService); appShell = mock(ApplicationShell); - appShell = mock(ApplicationShell); experimentsManager = mock(ExperimentsManager); executionFactory = mock(PythonExecutionFactory); pythonProcess = mock(PythonExecutionService); @@ -54,14 +64,7 @@ suite('Debugging - Adapter Factory', () => { debugAdapterPersistentState = mock(PersistentState); extensions = mock(Extensions); - const interpreter = { - architecture: Architecture.Unknown, - path: path.join('path', 'to', 'active', 'interpreter'), - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown - }; - + when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreter); when(interpreterService.getInterpreters(anything())).thenResolve([interpreter]); when(executionFactory.create(anything())).thenResolve(instance(pythonProcess)); when(extensions.getExtension(anything())).thenReturn({ packageJSON: { version: mockExtensionVersion } } as any); @@ -76,10 +79,10 @@ suite('Debugging - Adapter Factory', () => { ); }); - function mockPtvsdInfoPersistentState(sameVersion: boolean, ptvsdPath: string) { + function mockPtvsdInfoPersistentState(sameVersion: boolean) { const debugAdapterInfo: DebugAdapterPtvsdPathInfo = { extensionVersion: sameVersion ? mockExtensionVersion.raw : '2019.10.0-dev', ptvsdPath }; - when(stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined)).thenReturn(instance(debugAdapterPersistentState)); + when(stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined)).thenReturn(instance(debugAdapterPersistentState)); when(debugAdapterPersistentState.value).thenReturn(debugAdapterInfo); } @@ -95,21 +98,10 @@ suite('Debugging - Adapter Factory', () => { } test('Return the value of configuration.pythonPath as the current python path if it exists and if we are in the experiment', async () => { - const ptvsdPath = path.join('path', 'to', 'ptvsd'); - const pythonPath = path.join('session', 'path', 'to', 'python'); const session = createSession({ pythonPath }); - const interpreterDetails = { - architecture: Architecture.Unknown, - path: pythonPath, - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown, - version: new SemVer('3.7.4-test') - }; const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); - mockPtvsdInfoPersistentState(true, ptvsdPath); - when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); + mockPtvsdInfoPersistentState(true); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); @@ -118,23 +110,12 @@ suite('Debugging - Adapter Factory', () => { }); test('Return the path of the active interpreter as the current python path if we are in the experiment, it exists and configuration.pythonPath is not defined', async () => { - const ptvsdPath = path.join('path', 'to', 'ptvsd'); - const pythonPath = path.join('active', 'python', 'interpreter'); const session = createSession({}); - const interpreterDetails = { - architecture: Architecture.Unknown, - path: pythonPath, - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown, - version: new SemVer('3.7.4-test') - }; const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); - mockPtvsdInfoPersistentState(true, ptvsdPath); - when(interpreterService.getActiveInterpreter(anything())).thenResolve(interpreterDetails); - when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); + mockPtvsdInfoPersistentState(true); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + when(interpreterService.getActiveInterpreter(anything())).thenResolve(interpreter); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); @@ -142,22 +123,10 @@ suite('Debugging - Adapter Factory', () => { }); test('Return the path of the first available interpreter as the current python path if we are in the experiment, configuration.pythonPath is not defined and there is no active interpreter', async () => { - const ptvsdPath = path.join('path', 'to', 'ptvsd'); - const pythonPath = path.join('first', 'available', 'interpreter'); const session = createSession({}); - const interpreterDetails = { - architecture: Architecture.Unknown, - path: pythonPath, - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown, - version: new SemVer('3.7.4-test') - }; const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); - mockPtvsdInfoPersistentState(true, ptvsdPath); - when(interpreterService.getInterpreters(anything())).thenResolve([interpreterDetails]); - when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); + mockPtvsdInfoPersistentState(true); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); @@ -182,8 +151,8 @@ suite('Debugging - Adapter Factory', () => { }); test('Return old node debugger when the active interpreter is not Python 3.7', async () => { - const pythonPath = path.join('path', 'to', 'active', 'interpreter'); - const interpreterDetails = { + const python36Path = path.join('path', 'to', 'active', 'interpreter'); + const interpreterPython36Details = { architecture: Architecture.Unknown, path: pythonPath, sysPrefix: '', @@ -193,7 +162,7 @@ suite('Debugging - Adapter Factory', () => { }; const session = createSession({}); - when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); + when(interpreterService.getInterpreterDetails(python36Path)).thenResolve(interpreterPython36Details); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); @@ -201,21 +170,10 @@ suite('Debugging - Adapter Factory', () => { }); test('Return Python debug adapter executable when in the experiment and with the active interpreter being Python 3.7', async () => { - const ptvsdPath = path.join('path', 'to', 'ptvsd'); - const pythonPath = path.join('path', 'to', 'active', 'interpreter'); - const interpreterDetails = { - architecture: Architecture.Unknown, - path: pythonPath, - sysPrefix: '', - sysVersion: '', - type: InterpreterType.Unknown, - version: { raw: '3.7.4', major: 3, minor: 7, build: ['foo'], patch: 0, prerelease: ['bar'] } - }; const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); const session = createSession({}); - mockPtvsdInfoPersistentState(true, ptvsdPath); - when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreterDetails); + mockPtvsdInfoPersistentState(true); when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); From a2a0689d4c9076ede227db893ae96558f2f77a4f Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 5 Sep 2019 12:12:50 -0700 Subject: [PATCH 09/45] Don't mix then and await --- src/client/debugger/extension/adapter/factory.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index cf364471f9cf..d3fef5168b82 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -117,8 +117,10 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (!persistentState.value || version.raw !== persistentState.value.extensionVersion) { const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); const pythonProcess = await this.executionFactory.create({ pythonPath }); + const executionResult = await pythonProcess.exec([pathToScript], {}); + + pathToPtvsd = executionResult.stdout.trim(); - pathToPtvsd = await pythonProcess.exec([pathToScript], {}).then(output => output.stdout.trim()); await persistentState.updateValue({ extensionVersion: version.raw, ptvsdPath: pathToPtvsd }); } else { pathToPtvsd = persistentState.value.ptvsdPath; From 6380ee014b9cbe89a5e23fe1a5a804e2e4d244ae Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 5 Sep 2019 12:14:10 -0700 Subject: [PATCH 10/45] More tests --- .../extension/adapter/factory.unit.test.ts | 123 +++++++++++++++++- 1 file changed, 116 insertions(+), 7 deletions(-) diff --git a/src/test/debugger/extension/adapter/factory.unit.test.ts b/src/test/debugger/extension/adapter/factory.unit.test.ts index 1cda6eccef6c..7d579e308ab9 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -18,9 +18,10 @@ import { ExperimentsManager } from '../../../../client/common/experiments'; import { PersistentState, PersistentStateFactory } from '../../../../client/common/persistentState'; import { PythonExecutionFactory } from '../../../../client/common/process/pythonExecutionFactory'; import { PythonExecutionService } from '../../../../client/common/process/pythonProcess'; -import { IPythonExecutionFactory, IPythonExecutionService } from '../../../../client/common/process/types'; +import { IPythonExecutionFactory } from '../../../../client/common/process/types'; import { IExperimentsManager, IExtensions, IPersistentState, IPersistentStateFactory } from '../../../../client/common/types'; import { Architecture } from '../../../../client/common/utils/platform'; +import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; import { DebugAdapterDescriptorFactory, ptvsdPathStorageKey } from '../../../../client/debugger/extension/adapter/factory'; import { DebugAdapterPtvsdPathInfo, IDebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/types'; import { IInterpreterService, InterpreterType } from '../../../../client/interpreter/contracts'; @@ -28,15 +29,13 @@ import { InterpreterService } from '../../../../client/interpreter/interpreterSe use(chaiAsPromised); -// tslint:disable: no-any // tslint:disable-next-line: max-func-body-length -suite('Debugging - Adapter Factory', () => { +suite('Dummy', () => { let factory: IDebugAdapterDescriptorFactory; let interpreterService: IInterpreterService; let appShell: IApplicationShell; let experimentsManager: IExperimentsManager; let executionFactory: IPythonExecutionFactory; - let pythonProcess: IPythonExecutionService; let stateFactory: IPersistentStateFactory; let debugAdapterPersistentState: IPersistentState; let extensions: IExtensions; @@ -59,15 +58,14 @@ suite('Debugging - Adapter Factory', () => { appShell = mock(ApplicationShell); experimentsManager = mock(ExperimentsManager); executionFactory = mock(PythonExecutionFactory); - pythonProcess = mock(PythonExecutionService); stateFactory = mock(PersistentStateFactory); debugAdapterPersistentState = mock(PersistentState); extensions = mock(Extensions); + // tslint:disable-next-line: no-any + when(extensions.getExtension(anything())).thenReturn({ packageJSON: { version: mockExtensionVersion } } as any); when(interpreterService.getInterpreterDetails(pythonPath)).thenResolve(interpreter); when(interpreterService.getInterpreters(anything())).thenResolve([interpreter]); - when(executionFactory.create(anything())).thenResolve(instance(pythonProcess)); - when(extensions.getExtension(anything())).thenReturn({ packageJSON: { version: mockExtensionVersion } } as any); factory = new DebugAdapterDescriptorFactory( instance(interpreterService), @@ -187,4 +185,115 @@ suite('Debugging - Adapter Factory', () => { await expect(promise).to.eventually.be.rejectedWith('Debug Adapter Executable not provided'); }); + + test('Save the PTVSD path in persistent storage if it doesn\'t exist in the cache', async () => { + const persistentPtvsdPath = path.join('persistent', 'ptvsd', 'path'); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(persistentPtvsdPath, 'ptvsd', 'adapter')]); + const session = createSession({}); + let execCalled = false; + + when(stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined)).thenReturn(instance(debugAdapterPersistentState)); + when(debugAdapterPersistentState.value).thenReturn(undefined); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + const pythonExecService = ({ + exec: () => { + execCalled = true; + return Promise.resolve({ stdout: persistentPtvsdPath }); + } + // tslint:disable-next-line: no-any + } as any) as PythonExecutionService; + when(executionFactory.create(anything())).thenResolve(pythonExecService); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + assert.equal(execCalled, true); + verify(executionFactory.create(anything())).once(); + verify(debugAdapterPersistentState.updateValue(anything())).once(); + }); + + test('Save the PTVSD path in persistent storage if the extension version in the cache is different from the actual one', async () => { + const session = createSession({}); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + let execCalled = false; + + mockPtvsdInfoPersistentState(false); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + const pythonExecService = ({ + exec: () => { + execCalled = true; + return Promise.resolve({ stdout: ptvsdPath }); + } + // tslint:disable-next-line: no-any + } as any) as PythonExecutionService; + when(executionFactory.create(anything())).thenResolve(pythonExecService); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + assert.equal(execCalled, true); + verify(executionFactory.create(anything())).once(); + verify(debugAdapterPersistentState.updateValue(anything())).once(); + }); + + test('Use the cached path to PTVSD if it exists and the extension version hasn\'t changed since the value was saved', async () => { + const session = createSession({}); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + let execCalled = false; + + mockPtvsdInfoPersistentState(true); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + const pythonExecService = ({ + exec: () => { + execCalled = true; + return Promise.resolve({ stdout: ptvsdPath }); + } + // tslint:disable-next-line: no-any + } as any) as PythonExecutionService; + when(executionFactory.create(anything())).thenResolve(pythonExecService); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + assert.equal(execCalled, false); + verify(executionFactory.create(anything())).never(); + verify(debugAdapterPersistentState.updateValue(anything())).never(); + verify(debugAdapterPersistentState.value).thrice(); + }); + + test('Pass the --log-dir argument to PTVSD is configuration.logToFile is set', async () => { + const session = createSession({ logToFile: true }); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter'), '--log-dir', EXTENSION_ROOT_DIR]); + + mockPtvsdInfoPersistentState(true); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); + + test('Don\'t pass the --log-dir argument to PTVSD is configuration.logToFile is not set', async () => { + const session = createSession({}); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + + mockPtvsdInfoPersistentState(true); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); + + test('Don\'t pass the --log-dir argument to PTVSD is configuration.logToFile is set but false', async () => { + const session = createSession({ logToFile: false }); + const debugExecutable = new DebugAdapterExecutable(pythonPath, [path.join(ptvsdPath, 'ptvsd', 'adapter')]); + + mockPtvsdInfoPersistentState(true); + when(experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)).thenReturn(true); + + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepEqual(descriptor, debugExecutable); + }); }); From afa78daab3ef18c5526523011a4a86edcd981560 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 5 Sep 2019 12:15:26 -0700 Subject: [PATCH 11/45] Lol undo suite name change --- src/test/debugger/extension/adapter/factory.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 7d579e308ab9..de90d6e60f12 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -30,7 +30,7 @@ import { InterpreterService } from '../../../../client/interpreter/interpreterSe use(chaiAsPromised); // tslint:disable-next-line: max-func-body-length -suite('Dummy', () => { +suite('Debugging - Adapter Factory', () => { let factory: IDebugAdapterDescriptorFactory; let interpreterService: IInterpreterService; let appShell: IApplicationShell; From 21fe86ba8e0225b219c5c253fc89b4e240124f9d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 5 Sep 2019 13:28:40 -0700 Subject: [PATCH 12/45] don't call __iter__ directly --- pythonFiles/ptvsd_folder_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 8b7458a02f2f..c55c3f7de79f 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -20,7 +20,7 @@ def ptvsd_folder_name(): if package_requirement.name != "ptvsd": continue requirement_specifier = package_requirement.specifier - ptvsd_version = next(requirement_specifier.__iter__()).version + ptvsd_version = next(iter(requirement_specifier)).version sys.path.remove(PYTHONFILES_PATH) From 0c437bf0cb2ee395eccb9ea997a50eacb3b5a6cb Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Date: Fri, 6 Sep 2019 08:49:43 -0700 Subject: [PATCH 13/45] Update src/client/debugger/extension/adapter/factory.ts Co-Authored-By: Eric Snow --- src/client/debugger/extension/adapter/factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index d3fef5168b82..d6d3206dc282 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -126,6 +126,6 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac pathToPtvsd = persistentState.value.ptvsdPath; } - return new Promise(resolve => resolve(pathToPtvsd)); + return pathToPtvsd; } } From f7a07ab0c951d756ec597fdfb593382c2004728a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 6 Sep 2019 09:20:00 -0700 Subject: [PATCH 14/45] Renaming in python scripts (code review) --- pythonFiles/install_ptvsd.py | 30 +++++++++++++++--------------- pythonFiles/ptvsd_folder_name.py | 32 +++++++++++++++----------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index b42e1b5ab1d4..66336a0f20c9 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -5,25 +5,25 @@ import urllib.request import sys -ROOT_DIRNAME = path.dirname(path.dirname(path.abspath(__file__))) -REQUIREMENTS_PATH = path.join(ROOT_DIRNAME, "requirements.txt") -PYTHONFILES_PATH = path.join(ROOT_DIRNAME, "pythonFiles", "lib", "python") +ROOT = path.dirname(path.dirname(path.abspath(__file__))) +REQUIREMENTS = path.join(ROOT, "requirements.txt") +PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") PYPI_PTVSD_URL = "https://pypi.org/pypi/ptvsd/json" def install_ptvsd(): - # If we are in CI use the packaging module installed in PYTHONFILES_PATH. + # If we are in CI use the packaging module installed in PYTHONFILES. if len(sys.argv) == 2 and sys.argv[1] == "--ci": - sys.path.insert(0, PYTHONFILES_PATH) + sys.path.insert(0, PYTHONFILES) from packaging.requirements import Requirement - with open(REQUIREMENTS_PATH, "r", encoding="utf-8") as requirements: - for line in requirements: - package_requirement = Requirement(line) - if package_requirement.name != "ptvsd": - continue - requirement_specifier = package_requirement.specifier - ptvsd_version = next(requirement_specifier.__iter__()).version + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: + for line in reqsfile: + pkgreq = Requirement(line) + if pkgreq.name == "ptvsd": + specs = pkgreq.specifier + version = next(iter(specs)).version + break # Response format: https://warehouse.readthedocs.io/api-reference/json/#project with urllib.request.urlopen(PYPI_PTVSD_URL) as response: @@ -31,18 +31,18 @@ def install_ptvsd(): releases = json_response["releases"] # Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst - for wheel_info in releases[ptvsd_version]: + for wheel_info in releases[version]: # 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. - ptvsd_path = path.join(PYTHONFILES_PATH, filename) + ptvsd_path = path.join(PYTHONFILES, filename) with urllib.request.urlopen(wheel_info["url"]) as wheel_response: wheel_file = BytesIO(wheel_response.read()) # Extract only the contents of the purelib subfolder (parent folder of ptvsd), # since ptvsd files rely on the presence of a 'ptvsd' folder. - prefix = path.join(f"ptvsd-{ptvsd_version}.data", "purelib") + prefix = path.join(f"ptvsd-{version}.data", "purelib") with ZipFile(wheel_file, "r") as wheel: for zip_info in wheel.infolist(): diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index c55c3f7de79f..193ba47988f5 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -1,11 +1,11 @@ import sys from os import path -ROOT_DIRNAME = path.dirname(path.dirname(path.abspath(__file__))) -PYTHONFILES_PATH = path.join(ROOT_DIRNAME, "pythonFiles", "lib", "python") -REQUIREMENTS_PATH = path.join(ROOT_DIRNAME, "requirements.txt") +ROOT = path.dirname(path.dirname(path.abspath(__file__))) +PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") +REQUIREMENTS = path.join(ROOT, "requirements.txt") -sys.path.insert(0, PYTHONFILES_PATH) +sys.path.insert(0, PYTHONFILES) from packaging.tags import sys_tags from packaging.requirements import Requirement @@ -14,27 +14,25 @@ def ptvsd_folder_name(): """Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter.""" - with open(REQUIREMENTS_PATH, "r", encoding="utf-8") as requirements: - for line in requirements: - package_requirement = Requirement(line) - if package_requirement.name != "ptvsd": - continue - requirement_specifier = package_requirement.specifier - ptvsd_version = next(iter(requirement_specifier)).version + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: + for line in reqsfile: + pkgreq = Requirement(line) + if pkgreq.name == "ptvsd": + specs = pkgreq.specifier + version = next(iter(specs)).version + break - sys.path.remove(PYTHONFILES_PATH) + sys.path.remove(PYTHONFILES) for tag in sys_tags(): - folder_name = ( - f"ptvsd-{ptvsd_version}-{tag.interpreter}-{tag.abi}-{tag.platform}" - ) - folder_path = path.join(PYTHONFILES_PATH, folder_name) + folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" + folder_path = path.join(PYTHONFILES, folder_name) if path.exists(folder_path): print(folder_path) return # Fallback to use base PTVSD path. - print(PYTHONFILES_PATH) + print(PYTHONFILES) if __name__ == "__main__": From 01073df86d09b9f4f5c8fda167b65db8b1b49b57 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 6 Sep 2019 09:39:23 -0700 Subject: [PATCH 15/45] Add fallback to SpecifierSet --- pythonFiles/ptvsd_folder_name.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 193ba47988f5..1e717223ef95 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -19,7 +19,14 @@ def ptvsd_folder_name(): pkgreq = Requirement(line) if pkgreq.name == "ptvsd": specs = pkgreq.specifier - version = next(iter(specs)).version + try: + spec, = specs + except ValueError: + # Fallpack to use base PTVSD path. + print(PYTHONFILES) + return + else: + version = spec.version break sys.path.remove(PYTHONFILES) From 1fb07d77c6f6fb38613c72718e0d93456e9c95e4 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 6 Sep 2019 10:13:35 -0700 Subject: [PATCH 16/45] TS code review --- .../debugger/extension/adapter/factory.ts | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index d3fef5168b82..ca66c50f1384 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -33,13 +33,16 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac public async createDebugAdapterDescriptor(session: DebugSession, executable: DebugAdapterExecutable | undefined): Promise { const configuration = session.configuration as (LaunchRequestArguments | AttachRequestArguments); const pythonPath = await this.getPythonPath(configuration, session.workspaceFolder); - const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); - if (this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment) && interpreterInfo && interpreterInfo.version && interpreterInfo.version.raw.startsWith('3.7')) { + if (await this.useNewPtvsd(pythonPath)) { // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; - const ptvsdPathToUse = await this.getPtvsdFolder(pythonPath); - + let ptvsdPathToUse: string; + try { + ptvsdPathToUse = await this.getPtvsdFolder(pythonPath); + } catch { + ptvsdPathToUse = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); + } return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'ptvsd', 'adapter'), ...logArgs]); } @@ -50,6 +53,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac // Unlikely scenario. throw new Error('Debug Adapter Executable not provided'); } + /** * Get the python executable used to launch the Python Debug Adapter. * In the case of `attach` scenarios, just use the workspace interpreter, else first available one. @@ -58,7 +62,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac * @private * @param {(LaunchRequestArguments | AttachRequestArguments)} configuration * @param {WorkspaceFolder} [workspaceFolder] - * @returns {Promise} + * @returns {Promise} Path to the python interpreter for this workspace. * @memberof DebugAdapterDescriptorFactory */ private async getPythonPath(configuration: LaunchRequestArguments | AttachRequestArguments, workspaceFolder?: WorkspaceFolder): Promise { @@ -81,6 +85,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac traceVerbose(`Picking first available interpreter to launch the DA '${interpreters[0].path}'`); return interpreters[0].path; } + /** * Notify user about the requirement for Python. * Unlikely scenario, as ex expect users to have Python in order to use the extension. @@ -100,32 +105,51 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac * - It has never been computed before; * - The extension number has changed since the last time it was cached. * - * Return a cached path otherwise. + * Return a cached path otherwise, since we pin the PTVSD version with each extension release, + * and other factors on folder selection (like host platform) won't change. * * @private * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) - * @returns {Promise} + * @returns {Promise} Path to the PTVSD version to use in the debug adapter. * @memberof DebugAdapterDescriptorFactory */ private async getPtvsdFolder(pythonPath: string): Promise { const persistentState = this.stateFactory.createGlobalPersistentState(ptvsdPathStorageKey, undefined); - let pathToPtvsd = ''; - const extension = this.extensions.getExtension(PVSC_EXTENSION_ID)!; const version = parse(extension.packageJSON.version)!; - if (!persistentState.value || version.raw !== persistentState.value.extensionVersion) { - const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); - const pythonProcess = await this.executionFactory.create({ pythonPath }); - const executionResult = await pythonProcess.exec([pathToScript], {}); + if (persistentState.value && version.raw === persistentState.value.extensionVersion) { + return persistentState.value.ptvsdPath; + } + + // The ptvsd path wasn't cached, so run the script and cache it. + const pathToScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'ptvsd_folder_name.py'); + const pythonProcess = await this.executionFactory.create({ pythonPath }); + const executionResult = await pythonProcess.exec([pathToScript], {}); + const pathToPtvsd = executionResult.stdout.trim(); - pathToPtvsd = executionResult.stdout.trim(); + await persistentState.updateValue({ extensionVersion: version.raw, ptvsdPath: pathToPtvsd }); + + return pathToPtvsd; + } - await persistentState.updateValue({ extensionVersion: version.raw, ptvsdPath: pathToPtvsd }); - } else { - pathToPtvsd = persistentState.value.ptvsdPath; + /** + * Check and return whether the user is in the PTVSD wheels experiment or not. + * + * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) + * @returns {Promise} Whether the user is in the experiment or not. + * @memberof DebugAdapterDescriptorFactory + */ + private async useNewPtvsd(pythonPath: string): Promise { + if (!this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { + return false; + } + + const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); + if (!interpreterInfo || !interpreterInfo.version || !interpreterInfo.version.raw.startsWith('3.7')) { + return false; } - return new Promise(resolve => resolve(pathToPtvsd)); + return true; } } From e1df371bd0cd243fcb96bf6a6ffd260554779ad3 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 6 Sep 2019 13:01:14 -0700 Subject: [PATCH 17/45] Python unit tests + error handling --- pythonFiles/ptvsd_folder_name.py | 24 ++++---- pythonFiles/tests/test_ptvsd_folder_name.py | 64 +++++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 pythonFiles/tests/test_ptvsd_folder_name.py diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 1e717223ef95..70ef53015727 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -10,6 +10,8 @@ from packaging.tags import sys_tags from packaging.requirements import Requirement +sys.path.remove(PYTHONFILES) + def ptvsd_folder_name(): """Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter.""" @@ -21,22 +23,24 @@ def ptvsd_folder_name(): specs = pkgreq.specifier try: spec, = specs + version = spec.version except ValueError: # Fallpack to use base PTVSD path. print(PYTHONFILES) return - else: - version = spec.version break - sys.path.remove(PYTHONFILES) - - for tag in sys_tags(): - folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" - folder_path = path.join(PYTHONFILES, folder_name) - if path.exists(folder_path): - print(folder_path) - return + try: + for tag in sys_tags(): + folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" + folder_path = path.join(PYTHONFILES, folder_name) + if path.exists(folder_path): + print(folder_path) + return + except: + # Fallback to use base PTVSD path no matter the exception. + print(PYTHONFILES) + return # Fallback to use base PTVSD path. print(PYTHONFILES) diff --git a/pythonFiles/tests/test_ptvsd_folder_name.py b/pythonFiles/tests/test_ptvsd_folder_name.py new file mode 100644 index 000000000000..897ab4d2c781 --- /dev/null +++ b/pythonFiles/tests/test_ptvsd_folder_name.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import pytest +from os import path +from packaging.tags import sys_tags +from unittest.mock import patch, mock_open + +from pythonFiles.ptvsd_folder_name import ptvsd_folder_name + +ROOT = path.dirname(path.dirname(path.dirname(path.abspath(__file__)))) +PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") +REQUIREMENTS = path.join(path.dirname(path.abspath(__file__)), "ptvsd_folder_name") + + +def open_requirements_with_ptvsd(): + return patch( + "pythonFiles.ptvsd_folder_name.open", + mock_open(read_data="jedi==0.15.1\nptvsd==5.0.0"), + ) + + +def open_requirements_without_ptvsd(): + return patch( + "pythonFiles.ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n") + ) + + +def mock_path_exists_true(pathname): + return True + + +def mock_path_exists_false(pathname): + return False + + +class TestPtvsdFolderName(object): + """Unit tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" + + def test_requirement_exists_folder_exists(self, capsys, monkeypatch): + monkeypatch.setattr(path, "exists", mock_path_exists_true) + with open_requirements_with_ptvsd() as p: + ptvsd_folder_name() + tag = next(sys_tags()) + folder = f"ptvsd-5.0.0-{tag.interpreter}-{tag.abi}-{tag.platform}" + expected = path.join(PYTHONFILES, folder) + captured = capsys.readouterr() + assert captured.out.strip() == expected + + 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.strip() == expected + + def test_no_wheel_folder(self, capsys, monkeypatch): + monkeypatch.setattr(path, "exists", mock_path_exists_false) + with open_requirements_with_ptvsd() as p: + ptvsd_folder_name() + expected = PYTHONFILES + captured = capsys.readouterr() + assert captured.out.strip() == expected + From 118a0f2d3338f326c982c2bc555fc038b9d557ce Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 11 Sep 2019 08:02:17 -0700 Subject: [PATCH 18/45] Move to a folder and fix things --- pythonFiles/install_ptvsd.py | 6 +- pythonFiles/ptvsd_folder_name.py | 14 ++-- pythonFiles/tests/__main__.py | 2 + pythonFiles/tests/debug_adapter/__init__.py | 2 + .../test_ptvsd_folder_name.py | 41 ++++++++---- .../test_ptvsd_folder_name_functional.py | 67 +++++++++++++++++++ 6 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 pythonFiles/tests/debug_adapter/__init__.py rename pythonFiles/tests/{ => debug_adapter}/test_ptvsd_folder_name.py (55%) create mode 100644 pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index 66336a0f20c9..c3df8997f21e 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -46,10 +46,10 @@ def install_ptvsd(): with ZipFile(wheel_file, "r") as wheel: for zip_info in wheel.infolist(): - if not zip_info.filename.startswith(prefix): - continue + # Normalize path for Windows, the wheel folder structure uses forward slashes. + normalized = path.normpath(zip_info.filename) # Flatten the folder structure. - zip_info.filename = zip_info.filename.split(prefix)[-1] + zip_info.filename = normalized.split(prefix)[-1] wheel.extract(zip_info, ptvsd_path) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 70ef53015727..aed56d1095aa 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -7,8 +7,8 @@ sys.path.insert(0, PYTHONFILES) -from packaging.tags import sys_tags from packaging.requirements import Requirement +from packaging.tags import sys_tags sys.path.remove(PYTHONFILES) @@ -24,9 +24,9 @@ def ptvsd_folder_name(): try: spec, = specs version = spec.version - except ValueError: + except: # Fallpack to use base PTVSD path. - print(PYTHONFILES) + print(PYTHONFILES, end="") return break @@ -35,15 +35,15 @@ def ptvsd_folder_name(): folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" folder_path = path.join(PYTHONFILES, folder_name) if path.exists(folder_path): - print(folder_path) + print(folder_path, end="") return except: # Fallback to use base PTVSD path no matter the exception. - print(PYTHONFILES) + print(PYTHONFILES, end="") return - # Fallback to use base PTVSD path. - print(PYTHONFILES) + # Default fallback to use base PTVSD path. + print(PYTHONFILES, end="") if __name__ == "__main__": diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py index 5b140cc521bb..5c08ef151a13 100644 --- a/pythonFiles/tests/__main__.py +++ b/pythonFiles/tests/__main__.py @@ -13,6 +13,7 @@ PROJECT_ROOT = os.path.dirname(SRC_ROOT) IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') +DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, 'debug_adapter') def parse_args(): @@ -32,6 +33,7 @@ def parse_args(): def main(pytestargs, markers=None): sys.path.insert(1, IPYTHON_ROOT) sys.path.insert(1, TESTING_TOOLS_ROOT) + sys.path.insert(1, DEBUG_ADAPTER_ROOT) pytestargs = [ '--rootdir', SRC_ROOT, diff --git a/pythonFiles/tests/debug_adapter/__init__.py b/pythonFiles/tests/debug_adapter/__init__.py new file mode 100644 index 000000000000..5b7f7a925cc0 --- /dev/null +++ b/pythonFiles/tests/debug_adapter/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/pythonFiles/tests/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py similarity index 55% rename from pythonFiles/tests/test_ptvsd_folder_name.py rename to pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index 897ab4d2c781..f8d1728493ff 100644 --- a/pythonFiles/tests/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -2,28 +2,35 @@ # Licensed under the MIT License. import pytest +import sys + from os import path -from packaging.tags import sys_tags -from unittest.mock import patch, mock_open -from pythonFiles.ptvsd_folder_name import ptvsd_folder_name +try: + from unittest.mock import patch, mock_open + + from packaging.tags import sys_tags + from ptvsd_folder_name import ptvsd_folder_name +except: # Python 2.7 + print("Not importing anything for Python 2.7 since the test will be skipped.") -ROOT = path.dirname(path.dirname(path.dirname(path.abspath(__file__)))) + +ROOT = path.dirname(path.dirname(path.dirname(path.dirname(path.abspath(__file__))))) PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") -REQUIREMENTS = path.join(path.dirname(path.abspath(__file__)), "ptvsd_folder_name") + + +def isPython37(): + return sys.version_info.major == 3 and sys.version_info.minor == 7 def open_requirements_with_ptvsd(): return patch( - "pythonFiles.ptvsd_folder_name.open", - mock_open(read_data="jedi==0.15.1\nptvsd==5.0.0"), + "ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\nptvsd==5.0.0") ) def open_requirements_without_ptvsd(): - return patch( - "pythonFiles.ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n") - ) + return patch("ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n")) def mock_path_exists_true(pathname): @@ -34,31 +41,37 @@ def mock_path_exists_false(pathname): return False +@pytest.mark.skipif(not isPython37(), reason="PTVSD wheels shipped for Python 3.7 only") class TestPtvsdFolderName(object): """Unit tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" def test_requirement_exists_folder_exists(self, capsys, monkeypatch): + # Return the first constructed folder path as existing. monkeypatch.setattr(path, "exists", mock_path_exists_true) with open_requirements_with_ptvsd() as p: ptvsd_folder_name() tag = next(sys_tags()) - folder = f"ptvsd-5.0.0-{tag.interpreter}-{tag.abi}-{tag.platform}" + folder = "ptvsd-5.0.0-{0}-{1}-{2}".format( + tag.interpreter, tag.abi, tag.platform + ) expected = path.join(PYTHONFILES, folder) captured = capsys.readouterr() - assert captured.out.strip() == expected + assert captured.out == expected 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.strip() == expected + assert captured.out == expected def test_no_wheel_folder(self, capsys, monkeypatch): + # Return none of of the constructed paths as existing, + # ptvsd_folder_name() should return the path to default ptvsd. monkeypatch.setattr(path, "exists", mock_path_exists_false) with open_requirements_with_ptvsd() as p: ptvsd_folder_name() expected = PYTHONFILES captured = capsys.readouterr() - assert captured.out.strip() == expected + assert captured.out == expected diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py new file mode 100644 index 000000000000..c8518e2c6303 --- /dev/null +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -0,0 +1,67 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import pytest +import subprocess +import sys + +from os import path +from packaging.requirements import Requirement + + +ROOT = path.dirname(path.dirname(path.dirname(path.dirname(path.abspath(__file__))))) +PYTHONFILES_ROOT = path.join(ROOT, "pythonFiles") +PYTHONFILES = path.join(PYTHONFILES_ROOT, "lib", "python") +REQUIREMENTS = path.join(ROOT, "requirements.txt") +ARGV = ["python", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] + + +def isPython37(): + return sys.version_info.major == 3 and sys.version_info.minor == 7 + + +@pytest.mark.skipif(not isPython37(), reason="PTVSD wheels shipped for Python 3.7 only") +@pytest.mark.functional +class TestPtvsdFolderNameFunctional(object): + """Functional tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" + + @classmethod + def setup_class(cls): + cls.version = cls.ptvsd_version() + + def ptvsd_version(): + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: + for line in reqsfile: + pkgreq = Requirement(line) + if pkgreq.name == "ptvsd": + specs = pkgreq.specifier + return next(iter(specs)).version + + def ptvsd_paths(self, platforms=[]): + paths = set() + for platform in platforms: + folder = "ptvsd-{0}-cp37-cp37m-{1}".format(self.version, platform) + paths.add(path.join(PYTHONFILES, folder)) + return paths + + def test_ptvsd_folder_name_nofail(self): + output = subprocess.check_output(ARGV, universal_newlines=True) + assert output != PYTHONFILES + + @pytest.mark.skipif(sys.platform != "darwin", reason="macOS functional test") + def test_ptvsd_folder_name_macos(self): + output = subprocess.check_output(ARGV, universal_newlines=True) + platform = ["macosx_10_13_x86_64"] + assert output in self.ptvsd_paths(platform) + + @pytest.mark.skipif(sys.platform != "win32", reason="Windows functional test") + def test_ptvsd_folder_name_windows(self): + output = subprocess.check_output(ARGV, universal_newlines=True) + assert output in self.ptvsd_paths(["win32", "win_amd64"]) + + @pytest.mark.skipif(sys.platform != "linux", reason="Linux functional test") + def test_ptvsd_folder_name_linux(self): + output = subprocess.check_output(ARGV, universal_newlines=True) + assert output in self.ptvsd_paths( + ["manylinux1_i686", "manylinux1_x86_64", "manylinux2010_x86_64"] + ) From d4ac10c62562e6b91aaf6503dabd7a491e185aa2 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 12 Sep 2019 09:14:35 -0700 Subject: [PATCH 19/45] Use latest version of ptvsd --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index aefcfc2c437f..02c7f815e8b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jedi==0.15.1 parso==0.5.1 isort==4.3.21 -ptvsd==5.0.0a3 +ptvsd==5.0.0a4 pyparsing==2.4.0 six==1.12.0 packaging==19.1 From 913c2594ecc1c48a0735bb9fb90ba0aa8531d6d6 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 18 Sep 2019 08:43:03 -0700 Subject: [PATCH 20/45] Undo formatting changes --- .../extension/serviceRegistry.unit.test.ts | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index ddf783cbfa12..e7e4357a136a 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -58,23 +58,11 @@ suite('Debugging - Service Registry', () => { verify(serviceManager.addSingleton>(IDebugConfigurationResolver, LaunchConfigurationResolver, 'launch')).once(); verify(serviceManager.addSingleton>(IDebugConfigurationResolver, AttachConfigurationResolver, 'attach')).once(); verify(serviceManager.addSingleton(IDebugConfigurationProviderFactory, DebugConfigurationProviderFactory)).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, FileLaunchDebugConfigurationProvider, DebugConfigurationType.launchFile) - ).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, DjangoLaunchDebugConfigurationProvider, DebugConfigurationType.launchDjango) - ).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, FlaskLaunchDebugConfigurationProvider, DebugConfigurationType.launchFlask) - ).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, RemoteAttachDebugConfigurationProvider, DebugConfigurationType.remoteAttach) - ).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, ModuleLaunchDebugConfigurationProvider, DebugConfigurationType.launchModule) - ).once(); - verify( - serviceManager.addSingleton(IDebugConfigurationProvider, PyramidLaunchDebugConfigurationProvider, DebugConfigurationType.launchPyramid) - ).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, FileLaunchDebugConfigurationProvider, DebugConfigurationType.launchFile)).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, DjangoLaunchDebugConfigurationProvider, DebugConfigurationType.launchDjango)).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, FlaskLaunchDebugConfigurationProvider, DebugConfigurationType.launchFlask)).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, RemoteAttachDebugConfigurationProvider, DebugConfigurationType.remoteAttach)).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, ModuleLaunchDebugConfigurationProvider, DebugConfigurationType.launchModule)).once(); + verify(serviceManager.addSingleton(IDebugConfigurationProvider, PyramidLaunchDebugConfigurationProvider, DebugConfigurationType.launchPyramid)).once(); }); }); From c259c92439b3b551de9b78876face5e2083c85fb Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 18 Sep 2019 15:28:55 -0700 Subject: [PATCH 21/45] Update packaging version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 02c7f815e8b8..229c3e140f94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ isort==4.3.21 ptvsd==5.0.0a4 pyparsing==2.4.0 six==1.12.0 -packaging==19.1 +packaging==19.2 From 961474da5ea8c02c08d75e6478a30bee052df7bd Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Wed, 18 Sep 2019 16:47:06 -0700 Subject: [PATCH 22/45] Add path to pythonfiles --- pythonFiles/tests/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py index 5c08ef151a13..f9abe14f9c05 100644 --- a/pythonFiles/tests/__main__.py +++ b/pythonFiles/tests/__main__.py @@ -11,6 +11,7 @@ TEST_ROOT = os.path.dirname(__file__) SRC_ROOT = os.path.dirname(TEST_ROOT) PROJECT_ROOT = os.path.dirname(SRC_ROOT) +PYTHONFILES_LIB_ROOT = os.path.join(PROJECT_ROOT, 'lib') IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, 'debug_adapter') @@ -31,6 +32,7 @@ def parse_args(): def main(pytestargs, markers=None): + sys.path.insert(1, PYTHONFILES_LIB_ROOT) sys.path.insert(1, IPYTHON_ROOT) sys.path.insert(1, TESTING_TOOLS_ROOT) sys.path.insert(1, DEBUG_ADAPTER_ROOT) From cb30b0d964a890e67c0f6955a85b37cf42827246 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 08:10:28 -0700 Subject: [PATCH 23/45] forgot python subfolder --- pythonFiles/tests/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py index f9abe14f9c05..2ff245030330 100644 --- a/pythonFiles/tests/__main__.py +++ b/pythonFiles/tests/__main__.py @@ -11,7 +11,7 @@ TEST_ROOT = os.path.dirname(__file__) SRC_ROOT = os.path.dirname(TEST_ROOT) PROJECT_ROOT = os.path.dirname(SRC_ROOT) -PYTHONFILES_LIB_ROOT = os.path.join(PROJECT_ROOT, 'lib') +PYTHONFILES_LIB_ROOT = os.path.join(PROJECT_ROOT, 'lib', 'python') IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, 'debug_adapter') From 2375ffdaf602924980b11e457add7866ed7c8307 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 08:46:51 -0700 Subject: [PATCH 24/45] try updating pytest in test-requirements --- build/test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/test-requirements.txt b/build/test-requirements.txt index 37abe5e0a827..5dd324c3b6d6 100644 --- a/build/test-requirements.txt +++ b/build/test-requirements.txt @@ -10,7 +10,7 @@ pycodestyle prospector pydocstyle nose -pytest==3.6.3 +pytest==5.0.1 rope flask django From cd7a87d231e5ea6d9800d11f59e2dc4251501211 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 08:49:43 -0700 Subject: [PATCH 25/45] Add packaging to test requirements --- build/test-requirements.txt | 1 + pythonFiles/tests/__main__.py | 2 -- .../tests/debug_adapter/test_ptvsd_folder_name_functional.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/build/test-requirements.txt b/build/test-requirements.txt index 5dd324c3b6d6..a4c28cbb29b9 100644 --- a/build/test-requirements.txt +++ b/build/test-requirements.txt @@ -15,3 +15,4 @@ rope flask django isort +packaging==19.2 diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py index 2ff245030330..5c08ef151a13 100644 --- a/pythonFiles/tests/__main__.py +++ b/pythonFiles/tests/__main__.py @@ -11,7 +11,6 @@ TEST_ROOT = os.path.dirname(__file__) SRC_ROOT = os.path.dirname(TEST_ROOT) PROJECT_ROOT = os.path.dirname(SRC_ROOT) -PYTHONFILES_LIB_ROOT = os.path.join(PROJECT_ROOT, 'lib', 'python') IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, 'debug_adapter') @@ -32,7 +31,6 @@ def parse_args(): def main(pytestargs, markers=None): - sys.path.insert(1, PYTHONFILES_LIB_ROOT) sys.path.insert(1, IPYTHON_ROOT) sys.path.insert(1, TESTING_TOOLS_ROOT) sys.path.insert(1, DEBUG_ADAPTER_ROOT) 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 c8518e2c6303..09afc78835f0 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -13,7 +13,7 @@ PYTHONFILES_ROOT = path.join(ROOT, "pythonFiles") PYTHONFILES = path.join(PYTHONFILES_ROOT, "lib", "python") REQUIREMENTS = path.join(ROOT, "requirements.txt") -ARGV = ["python", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] +ARGV = ["python3", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] def isPython37(): From 893fed9edb5ed112516c7ea38c1f06c0d100c80c Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 08:53:25 -0700 Subject: [PATCH 26/45] Forgot copyright --- pythonFiles/ptvsd_folder_name.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index aed56d1095aa..9adc06744e54 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + import sys from os import path From add361ddf996bda7645b8730d8299084dbb262df Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 09:25:19 -0700 Subject: [PATCH 27/45] Undo pytest version change --- build/test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/test-requirements.txt b/build/test-requirements.txt index a4c28cbb29b9..ffc4cf1d3287 100644 --- a/build/test-requirements.txt +++ b/build/test-requirements.txt @@ -10,7 +10,7 @@ pycodestyle prospector pydocstyle nose -pytest==5.0.1 +pytest==3.6.3 rope flask django From 24841be9a48e1327649770a2722398aa8a1c306d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 09:44:31 -0700 Subject: [PATCH 28/45] Install ptvsd wheels during tests step not compile --- build/ci/templates/steps/build_compile.yml | 9 --------- build/ci/templates/test_phases.yml | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build/ci/templates/steps/build_compile.yml b/build/ci/templates/steps/build_compile.yml index 1ada0fcb400c..5abf5c0bcdaa 100644 --- a/build/ci/templates/steps/build_compile.yml +++ b/build/ci/templates/steps/build_compile.yml @@ -36,15 +36,6 @@ steps: displayName: "pip install requirements" condition: and(succeeded(), eq(variables['build'], 'true')) - - task: PythonScript@0 - displayName: "Install PTVSD wheels" - inputs: - scriptSource: "filePath" - scriptPath: "./pythonFiles/install_ptvsd.py" - arguments: "--ci" - failOnStderr: true - condition: and(succeeded(), eq(variables['build'], 'true')) - - bash: npm run clean displayName: "Clean" condition: and(succeeded(), eq(variables['build'], 'true')) diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index 532be9bef2a6..c97c6ecf6e5f 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -138,6 +138,15 @@ steps: displayName: 'pip install ipython requirements' condition: and(succeeded(), eq(variables['NeedsIPythonReqs'], 'true')) + - task: PythonScript@0 + displayName: "Install PTVSD wheels" + inputs: + scriptSource: "filePath" + scriptPath: "./pythonFiles/install_ptvsd.py" + arguments: "--ci" + failOnStderr: true + condition: contains(variables['TestsToRun'], 'testUnitTests') + # Run the Python unit tests in our codebase. Produces a JUnit-style log file that # will be uploaded after all tests are complete. # From 4a000dafd4784a4fcea7aa72f30795fa20e633d6 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 09:48:39 -0700 Subject: [PATCH 29/45] yml indentation --- build/ci/templates/test_phases.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index c97c6ecf6e5f..1b4e9fd15657 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -139,13 +139,13 @@ steps: condition: and(succeeded(), eq(variables['NeedsIPythonReqs'], 'true')) - task: PythonScript@0 - displayName: "Install PTVSD wheels" - inputs: - scriptSource: "filePath" - scriptPath: "./pythonFiles/install_ptvsd.py" - arguments: "--ci" - failOnStderr: true - condition: contains(variables['TestsToRun'], 'testUnitTests') + displayName: "Install PTVSD wheels" + inputs: + scriptSource: "filePath" + scriptPath: "./pythonFiles/install_ptvsd.py" + arguments: "--ci" + failOnStderr: true + condition: contains(variables['TestsToRun'], 'testUnitTests') # Run the Python unit tests in our codebase. Produces a JUnit-style log file that # will be uploaded after all tests are complete. From 47366282f2e2ff83721131248160d22b11a912a5 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Date: Thu, 19 Sep 2019 09:58:42 -0700 Subject: [PATCH 30/45] Apply suggestions from code review Co-Authored-By: Eric Snow --- pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index f8d1728493ff..264308b20c63 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -20,7 +20,7 @@ def isPython37(): - return sys.version_info.major == 3 and sys.version_info.minor == 7 + return sys.version_info[:2] == (3, 7) def open_requirements_with_ptvsd(): @@ -47,8 +47,8 @@ class TestPtvsdFolderName(object): def test_requirement_exists_folder_exists(self, capsys, monkeypatch): # Return the first constructed folder path as existing. - monkeypatch.setattr(path, "exists", mock_path_exists_true) - with open_requirements_with_ptvsd() as p: + monkeypatch.setattr(path, "exists", lambda p: True) + with open_requirements_with_ptvsd(): ptvsd_folder_name() tag = next(sys_tags()) folder = "ptvsd-5.0.0-{0}-{1}-{2}".format( @@ -68,7 +68,7 @@ def test_no_ptvsd_requirement(self, capsys): def test_no_wheel_folder(self, capsys, monkeypatch): # Return none of of the constructed paths as existing, # ptvsd_folder_name() should return the path to default ptvsd. - monkeypatch.setattr(path, "exists", mock_path_exists_false) + monkeypatch.setattr(path, "exists", lambda p: False) with open_requirements_with_ptvsd() as p: ptvsd_folder_name() expected = PYTHONFILES From de95971bf9f6576805cd415715e88931ce62cf31 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 10:45:07 -0700 Subject: [PATCH 31/45] More code review changes --- .../debug_adapter/test_ptvsd_folder_name.py | 42 ++++++------- .../test_ptvsd_folder_name_functional.py | 61 ++++++++++--------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index 264308b20c63..d78833cd1f27 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -1,10 +1,14 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. - -import pytest import sys -from os import path +if sys.version_info[:2] != (3, 7): + import unittest + + raise unittest.SkipTest("PTVSD wheels shipped for Python 3.7 only") + +import os.path +import pytest try: from unittest.mock import patch, mock_open @@ -15,12 +19,10 @@ print("Not importing anything for Python 2.7 since the test will be skipped.") -ROOT = path.dirname(path.dirname(path.dirname(path.dirname(path.abspath(__file__))))) -PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") - - -def isPython37(): - return sys.version_info[:2] == (3, 7) +ROOT = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +PYTHONFILES = os.path.join(ROOT, "pythonFiles", "lib", "python") def open_requirements_with_ptvsd(): @@ -33,34 +35,28 @@ def open_requirements_without_ptvsd(): return patch("ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n")) -def mock_path_exists_true(pathname): - return True - - -def mock_path_exists_false(pathname): - return False - - -@pytest.mark.skipif(not isPython37(), reason="PTVSD wheels shipped for Python 3.7 only") -class TestPtvsdFolderName(object): +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, monkeypatch): # Return the first constructed folder path as existing. monkeypatch.setattr(path, "exists", lambda p: True) - with open_requirements_with_ptvsd(): - ptvsd_folder_name() tag = next(sys_tags()) folder = "ptvsd-5.0.0-{0}-{1}-{2}".format( tag.interpreter, tag.abi, tag.platform ) - expected = path.join(PYTHONFILES, folder) + + with open_requirements_with_ptvsd(): + ptvsd_folder_name() + + expected = os.path.join(PYTHONFILES, folder) captured = capsys.readouterr() assert captured.out == expected 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 @@ -69,8 +65,10 @@ def test_no_wheel_folder(self, capsys, monkeypatch): # Return none of of the constructed paths as existing, # ptvsd_folder_name() should return the path to default ptvsd. monkeypatch.setattr(path, "exists", lambda p: False) + with open_requirements_with_ptvsd() as p: ptvsd_folder_name() + expected = PYTHONFILES captured = capsys.readouterr() assert captured.out == expected 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 09afc78835f0..016d645e6366 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -1,9 +1,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import sys + +if sys.version_info[:2] != (3, 7): + import unittest + + raise unittest.SkipTest("PTVSD wheels shipped for Python 3.7 only") + import pytest import subprocess -import sys from os import path from packaging.requirements import Requirement @@ -13,37 +19,33 @@ PYTHONFILES_ROOT = path.join(ROOT, "pythonFiles") PYTHONFILES = path.join(PYTHONFILES_ROOT, "lib", "python") REQUIREMENTS = path.join(ROOT, "requirements.txt") -ARGV = ["python3", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] +ARGV = ["python", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] -def isPython37(): - return sys.version_info.major == 3 and sys.version_info.minor == 7 +def ptvsd_version(): + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: + for line in reqsfile: + pkgreq = Requirement(line) + if pkgreq.name == "ptvsd": + specs = pkgreq.specifier + return next(iter(specs)).version + + +VERSION = ptvsd_version() + + +def ptvsd_paths(*platforms): + paths = set() + for platform in platforms: + folder = "ptvsd-{0}-cp37-cp37m-{1}".format(VERSION, platform) + paths.add(path.join(PYTHONFILES, folder)) + return paths -@pytest.mark.skipif(not isPython37(), reason="PTVSD wheels shipped for Python 3.7 only") @pytest.mark.functional -class TestPtvsdFolderNameFunctional(object): +class TestPtvsdFolderNameFunctional: """Functional tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" - @classmethod - def setup_class(cls): - cls.version = cls.ptvsd_version() - - def ptvsd_version(): - with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: - for line in reqsfile: - pkgreq = Requirement(line) - if pkgreq.name == "ptvsd": - specs = pkgreq.specifier - return next(iter(specs)).version - - def ptvsd_paths(self, platforms=[]): - paths = set() - for platform in platforms: - folder = "ptvsd-{0}-cp37-cp37m-{1}".format(self.version, platform) - paths.add(path.join(PYTHONFILES, folder)) - return paths - def test_ptvsd_folder_name_nofail(self): output = subprocess.check_output(ARGV, universal_newlines=True) assert output != PYTHONFILES @@ -51,17 +53,16 @@ def test_ptvsd_folder_name_nofail(self): @pytest.mark.skipif(sys.platform != "darwin", reason="macOS functional test") def test_ptvsd_folder_name_macos(self): output = subprocess.check_output(ARGV, universal_newlines=True) - platform = ["macosx_10_13_x86_64"] - assert output in self.ptvsd_paths(platform) + assert output in ptvsd_paths("macosx_10_13_x86_64") @pytest.mark.skipif(sys.platform != "win32", reason="Windows functional test") def test_ptvsd_folder_name_windows(self): output = subprocess.check_output(ARGV, universal_newlines=True) - assert output in self.ptvsd_paths(["win32", "win_amd64"]) + assert output in ptvsd_paths("win32", "win_amd64") @pytest.mark.skipif(sys.platform != "linux", reason="Linux functional test") def test_ptvsd_folder_name_linux(self): output = subprocess.check_output(ARGV, universal_newlines=True) - assert output in self.ptvsd_paths( - ["manylinux1_i686", "manylinux1_x86_64", "manylinux2010_x86_64"] + assert output in ptvsd_paths( + "manylinux1_i686", "manylinux1_x86_64", "manylinux2010_x86_64" ) From e07a5a89baaf7c83b6b8655bd516a6fe0a73e7ef Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 11:22:25 -0700 Subject: [PATCH 32/45] more review changes --- pythonFiles/tests/__init__.py | 8 ++++++ pythonFiles/tests/__main__.py | 28 +++++++------------ .../debug_adapter/test_ptvsd_folder_name.py | 8 ++---- .../test_ptvsd_folder_name_functional.py | 16 ++++++----- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/pythonFiles/tests/__init__.py b/pythonFiles/tests/__init__.py index 5b7f7a925cc0..bfa6a6501f54 100644 --- a/pythonFiles/tests/__init__.py +++ b/pythonFiles/tests/__init__.py @@ -1,2 +1,10 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import os.path + +TEST_ROOT = os.path.dirname(__file__) +SRC_ROOT = os.path.dirname(TEST_ROOT) +PROJECT_ROOT = os.path.dirname(SRC_ROOT) +IPYTHON_ROOT = os.path.join(SRC_ROOT, "ipython") +TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, "testing_tools") +DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, "debug_adapter") diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py index 5c08ef151a13..14086978c9af 100644 --- a/pythonFiles/tests/__main__.py +++ b/pythonFiles/tests/__main__.py @@ -2,27 +2,22 @@ # Licensed under the MIT License. import argparse -import os.path import sys import pytest - -TEST_ROOT = os.path.dirname(__file__) -SRC_ROOT = os.path.dirname(TEST_ROOT) -PROJECT_ROOT = os.path.dirname(SRC_ROOT) -IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') -TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') -DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, 'debug_adapter') +from . import DEBUG_ADAPTER_ROOT, IPYTHON_ROOT, SRC_ROOT, TEST_ROOT, TESTING_TOOLS_ROOT def parse_args(): parser = argparse.ArgumentParser() # To mark a test as functional: (decorator) @pytest.mark.functional - parser.add_argument('--functional', dest='markers', - action='append_const', const='functional') - parser.add_argument('--no-functional', dest='markers', - action='append_const', const='not functional') + parser.add_argument( + "--functional", dest="markers", action="append_const", const="functional" + ) + parser.add_argument( + "--no-functional", dest="markers", action="append_const", const="not functional" + ) args, remainder = parser.parse_known_args() ns = vars(args) @@ -35,19 +30,16 @@ def main(pytestargs, markers=None): sys.path.insert(1, TESTING_TOOLS_ROOT) sys.path.insert(1, DEBUG_ADAPTER_ROOT) - pytestargs = [ - '--rootdir', SRC_ROOT, - TEST_ROOT, - ] + pytestargs + pytestargs = ["--rootdir", SRC_ROOT, TEST_ROOT] + pytestargs for marker in reversed(markers or ()): pytestargs.insert(0, marker) - pytestargs.insert(0, '-m') + pytestargs.insert(0, "-m") ec = pytest.main(pytestargs) return ec -if __name__ == '__main__': +if __name__ == "__main__": mainkwargs, pytestargs = parse_args() ec = main(pytestargs, **mainkwargs) sys.exit(ec) diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index d78833cd1f27..4cabf903c502 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -40,11 +40,9 @@ class TestPtvsdFolderName: def test_requirement_exists_folder_exists(self, capsys, monkeypatch): # Return the first constructed folder path as existing. - monkeypatch.setattr(path, "exists", lambda p: True) + monkeypatch.setattr(os.path, "exists", lambda p: True) tag = next(sys_tags()) - folder = "ptvsd-5.0.0-{0}-{1}-{2}".format( - tag.interpreter, tag.abi, tag.platform - ) + folder = "ptvsd-5.0.0-{}-{}-{}".format(tag.interpreter, tag.abi, tag.platform) with open_requirements_with_ptvsd(): ptvsd_folder_name() @@ -64,7 +62,7 @@ def test_no_ptvsd_requirement(self, capsys): def test_no_wheel_folder(self, capsys, monkeypatch): # Return none of of the constructed paths as existing, # ptvsd_folder_name() should return the path to default ptvsd. - monkeypatch.setattr(path, "exists", lambda p: False) + monkeypatch.setattr(os.path, "exists", lambda p: False) with open_requirements_with_ptvsd() as p: ptvsd_folder_name() 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 016d645e6366..99f6bb2e17fa 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -8,18 +8,20 @@ raise unittest.SkipTest("PTVSD wheels shipped for Python 3.7 only") +import os.path import pytest import subprocess -from os import path from packaging.requirements import Requirement -ROOT = path.dirname(path.dirname(path.dirname(path.dirname(path.abspath(__file__))))) -PYTHONFILES_ROOT = path.join(ROOT, "pythonFiles") -PYTHONFILES = path.join(PYTHONFILES_ROOT, "lib", "python") -REQUIREMENTS = path.join(ROOT, "requirements.txt") -ARGV = ["python", path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] +ROOT = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +PYTHONFILES_ROOT = os.path.join(ROOT, "pythonFiles") +PYTHONFILES = os.path.join(PYTHONFILES_ROOT, "lib", "python") +REQUIREMENTS = os.path.join(ROOT, "requirements.txt") +ARGV = ["python", os.path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] def ptvsd_version(): @@ -38,7 +40,7 @@ def ptvsd_paths(*platforms): paths = set() for platform in platforms: folder = "ptvsd-{0}-cp37-cp37m-{1}".format(VERSION, platform) - paths.add(path.join(PYTHONFILES, folder)) + paths.add(os.path.join(PYTHONFILES, folder)) return paths From 578304e62160894e7c0834351e5a3107f2d14c2a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 14:00:47 -0700 Subject: [PATCH 33/45] More review changes --- pythonFiles/install_ptvsd.py | 5 +++++ .../tests/debug_adapter/test_ptvsd_folder_name.py | 14 ++++++-------- .../test_ptvsd_folder_name_functional.py | 12 ++++-------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index c3df8997f21e..abdd3663a870 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -25,6 +25,11 @@ def install_ptvsd(): version = next(iter(specs)).version break + try: + version + except NameError: + raise ValueError("PTVSD was not found in requirements.txt") + # Response format: https://warehouse.readthedocs.io/api-reference/json/#project with urllib.request.urlopen(PYPI_PTVSD_URL) as response: json_response = json.loads(response.read()) diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index 4cabf903c502..0c3925a74d07 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -18,11 +18,9 @@ except: # Python 2.7 print("Not importing anything for Python 2.7 since the test will be skipped.") +from .. import SRC_ROOT -ROOT = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -) -PYTHONFILES = os.path.join(ROOT, "pythonFiles", "lib", "python") +PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") def open_requirements_with_ptvsd(): @@ -38,9 +36,9 @@ def open_requirements_without_ptvsd(): 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, monkeypatch): + def test_requirement_exists_folder_exists(self, capsys): # Return the first constructed folder path as existing. - monkeypatch.setattr(os.path, "exists", lambda p: True) + patch("os.path.exists", lambda p: True) tag = next(sys_tags()) folder = "ptvsd-5.0.0-{}-{}-{}".format(tag.interpreter, tag.abi, tag.platform) @@ -59,10 +57,10 @@ def test_no_ptvsd_requirement(self, capsys): captured = capsys.readouterr() assert captured.out == expected - def test_no_wheel_folder(self, capsys, monkeypatch): + 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. - monkeypatch.setattr(os.path, "exists", lambda p: False) + patch("os.path.exists", lambda p: False) with open_requirements_with_ptvsd() as p: ptvsd_folder_name() 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 99f6bb2e17fa..a1e3da71a87e 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -12,16 +12,12 @@ import pytest import subprocess +from .. import PROJECT_ROOT, SRC_ROOT from packaging.requirements import Requirement - -ROOT = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -) -PYTHONFILES_ROOT = os.path.join(ROOT, "pythonFiles") -PYTHONFILES = os.path.join(PYTHONFILES_ROOT, "lib", "python") -REQUIREMENTS = os.path.join(ROOT, "requirements.txt") -ARGV = ["python", os.path.join(PYTHONFILES_ROOT, "ptvsd_folder_name.py")] +PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") +REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") +ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] def ptvsd_version(): From 0f7cf05b20d0009a15425fdc16319a7b0eef9355 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 14:26:03 -0700 Subject: [PATCH 34/45] more changes --- pythonFiles/install_ptvsd.py | 2 +- .../tests/debug_adapter/test_ptvsd_folder_name.py | 11 ++++++++++- .../test_ptvsd_folder_name_functional.py | 9 ++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pythonFiles/install_ptvsd.py b/pythonFiles/install_ptvsd.py index abdd3663a870..5521b4e8dd4d 100644 --- a/pythonFiles/install_ptvsd.py +++ b/pythonFiles/install_ptvsd.py @@ -28,7 +28,7 @@ def install_ptvsd(): try: version except NameError: - raise ValueError("PTVSD was not found in requirements.txt") + raise Exception("ptvsd requirement not found.") # Response format: https://warehouse.readthedocs.io/api-reference/json/#project with urllib.request.urlopen(PYPI_PTVSD_URL) as response: diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index 0c3925a74d07..fc13970e39a3 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -18,9 +18,10 @@ except: # Python 2.7 print("Not importing anything for Python 2.7 since the test will be skipped.") -from .. import SRC_ROOT +from .. import SRC_ROOT, PROJECT_ROOT PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") +REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") def open_requirements_with_ptvsd(): @@ -49,6 +50,14 @@ def test_requirement_exists_folder_exists(self, capsys): 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() 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 a1e3da71a87e..ddf15607cd20 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -18,15 +18,14 @@ PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] +PREFIX = "ptvsd==" def ptvsd_version(): with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: for line in reqsfile: - pkgreq = Requirement(line) - if pkgreq.name == "ptvsd": - specs = pkgreq.specifier - return next(iter(specs)).version + if line.startswith(PREFIX): + return line[len(PREFIX) :].strip() VERSION = ptvsd_version() @@ -35,7 +34,7 @@ def ptvsd_version(): def ptvsd_paths(*platforms): paths = set() for platform in platforms: - folder = "ptvsd-{0}-cp37-cp37m-{1}".format(VERSION, platform) + folder = "ptvsd-{}-cp37-cp37m-{}".format(VERSION, platform) paths.add(os.path.join(PYTHONFILES, folder)) return paths From aae89c2acafd492edbb0ad474158e8c60c50d07a Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 14:27:20 -0700 Subject: [PATCH 35/45] remove need for ptvsd_version function --- .../test_ptvsd_folder_name_functional.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) 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 ddf15607cd20..6499a478ff45 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -20,15 +20,10 @@ ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] PREFIX = "ptvsd==" - -def ptvsd_version(): - with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: - for line in reqsfile: - if line.startswith(PREFIX): - return line[len(PREFIX) :].strip() - - -VERSION = ptvsd_version() +with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: + for line in reqsfile: + if line.startswith(PREFIX): + VERSION = line[len(PREFIX) :].strip() def ptvsd_paths(*platforms): From 1f2a3069abc38286140304e5fc56116f277403bd Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 15:07:53 -0700 Subject: [PATCH 36/45] Forgot to import re --- pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index fc13970e39a3..5ac535ea3dcb 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -9,6 +9,7 @@ import os.path import pytest +import re try: from unittest.mock import patch, mock_open From 9c1d2bb74b6cb36780dbbc178bbefb8e79e5e29d Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Thu, 19 Sep 2019 15:59:52 -0700 Subject: [PATCH 37/45] Fix tests --- pythonFiles/ptvsd_folder_name.py | 12 ++++++------ .../tests/debug_adapter/test_ptvsd_folder_name.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pythonFiles/ptvsd_folder_name.py b/pythonFiles/ptvsd_folder_name.py index 9adc06744e54..8b8dce53c52a 100644 --- a/pythonFiles/ptvsd_folder_name.py +++ b/pythonFiles/ptvsd_folder_name.py @@ -2,11 +2,11 @@ # Licensed under the MIT License. import sys -from os import path +import os.path -ROOT = path.dirname(path.dirname(path.abspath(__file__))) -PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") -REQUIREMENTS = path.join(ROOT, "requirements.txt") +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) @@ -36,8 +36,8 @@ def ptvsd_folder_name(): try: for tag in sys_tags(): folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" - folder_path = path.join(PYTHONFILES, folder_name) - if path.exists(folder_path): + folder_path = os.path.join(PYTHONFILES, folder_name) + if os.path.exists(folder_path): print(folder_path, end="") return except: diff --git a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py index 5ac535ea3dcb..1d6f834a0eb4 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -40,13 +40,17 @@ class TestPtvsdFolderName: def test_requirement_exists_folder_exists(self, capsys): # Return the first constructed folder path as existing. - patch("os.path.exists", lambda p: True) + + 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) with open_requirements_with_ptvsd(): ptvsd_folder_name() + patcher.stop() expected = os.path.join(PYTHONFILES, folder) captured = capsys.readouterr() assert captured.out == expected @@ -70,11 +74,14 @@ def test_no_ptvsd_requirement(self, capsys): 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. - patch("os.path.exists", lambda p: False) + patcher = patch("os.path.exists") + mock_no_exist = patcher.start() + mock_no_exist.side_effect = lambda p: False with open_requirements_with_ptvsd() as p: ptvsd_folder_name() + patcher.stop() expected = PYTHONFILES captured = capsys.readouterr() assert captured.out == expected From 14eef994e1684655edb59ac26de06e2f312e3fd0 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Date: Thu, 19 Sep 2019 16:58:13 -0700 Subject: [PATCH 38/45] Apply suggestions from code review Co-Authored-By: Eric Snow --- .../tests/debug_adapter/test_ptvsd_folder_name_functional.py | 1 + 1 file changed, 1 insertion(+) 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 6499a478ff45..90b1aaf17371 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -24,6 +24,7 @@ for line in reqsfile: if line.startswith(PREFIX): VERSION = line[len(PREFIX) :].strip() + break def ptvsd_paths(*platforms): From 4837f828f2f3be24ded141920d120ce3fc568588 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 20 Sep 2019 10:24:05 -0700 Subject: [PATCH 39/45] move path constants to __init__.py --- pythonFiles/tests/__init__.py | 3 +++ .../tests/debug_adapter/test_ptvsd_folder_name.py | 15 ++++----------- .../test_ptvsd_folder_name_functional.py | 4 +--- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pythonFiles/tests/__init__.py b/pythonFiles/tests/__init__.py index bfa6a6501f54..a400865e05bd 100644 --- a/pythonFiles/tests/__init__.py +++ b/pythonFiles/tests/__init__.py @@ -8,3 +8,6 @@ IPYTHON_ROOT = os.path.join(SRC_ROOT, "ipython") TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, "testing_tools") 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 1d6f834a0eb4..e823d74d8e95 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name.py @@ -11,18 +11,11 @@ import pytest import re -try: - from unittest.mock import patch, mock_open +from unittest.mock import patch, mock_open +from packaging.tags import sys_tags +from ptvsd_folder_name import ptvsd_folder_name - from packaging.tags import sys_tags - from ptvsd_folder_name import ptvsd_folder_name -except: # Python 2.7 - print("Not importing anything for Python 2.7 since the test will be skipped.") - -from .. import SRC_ROOT, PROJECT_ROOT - -PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") -REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") +from .. import PYTHONFILES, REQUIREMENTS def open_requirements_with_ptvsd(): 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 6499a478ff45..e0229d530caf 100644 --- a/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py +++ b/pythonFiles/tests/debug_adapter/test_ptvsd_folder_name_functional.py @@ -12,11 +12,9 @@ import pytest import subprocess -from .. import PROJECT_ROOT, SRC_ROOT from packaging.requirements import Requirement +from .. import PYTHONFILES, REQUIREMENTS, SRC_ROOT -PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") -REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] PREFIX = "ptvsd==" From 0b0d68e48e166557cc6c8f11c4d12337b6d58f56 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 20 Sep 2019 11:15:34 -0700 Subject: [PATCH 40/45] Add safeguard for ptvsd version during dev --- src/client/debugger/extension/adapter/factory.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index ca66c50f1384..6bf68b0c84ef 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -119,7 +119,14 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac const version = parse(extension.packageJSON.version)!; if (persistentState.value && version.raw === persistentState.value.extensionVersion) { - return persistentState.value.ptvsdPath; + const cachedPath = persistentState.value.ptvsdPath; + const access = promisify(fs.access); + try { + await access(cachedPath, fs.constants.F_OK); + return cachedPath; + } catch (err) { + // The path to the cached folder didn't exist (ptvsd requirements updated during development), so run the script again. + } } // The ptvsd path wasn't cached, so run the script and cache it. From d9288ffca4742e4013b6e15cc8388f724c48b9aa Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 20 Sep 2019 11:18:58 -0700 Subject: [PATCH 41/45] Add wheel support for remote debugging --- src/client/api.ts | 4 +- .../debugger/extension/adapter/factory.ts | 65 +++++++++++-------- src/client/extension.ts | 16 ++++- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/client/api.ts b/src/client/api.ts index 177dc997397d..ce71b1770825 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -33,7 +33,7 @@ export interface IExtensionApi { } // tslint:disable-next-line:no-any -export function buildApi(ready: Promise) { +export function buildApi(ready: Promise, ptvsdWheelsScriptPath?: string) { return { // 'ready' will propogate the exception, but we must log it here first. ready: ready.catch((ex) => { @@ -42,7 +42,7 @@ export function buildApi(ready: Promise) { }), debug: { async getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean = true): Promise { - return new RemoteDebuggerExternalLauncherScriptProvider().getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); + return new RemoteDebuggerExternalLauncherScriptProvider(ptvsdWheelsScriptPath).getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); } } }; diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 6bf68b0c84ef..243030663575 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -3,9 +3,11 @@ 'use strict'; +import * as fs from 'fs'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import { parse } from 'semver'; +import { promisify } from 'util'; import { DebugAdapterDescriptor, DebugAdapterExecutable, DebugSession, WorkspaceFolder } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; import { PVSC_EXTENSION_ID } from '../../../common/constants'; @@ -37,13 +39,8 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (await this.useNewPtvsd(pythonPath)) { // If logToFile is set in the debug config then pass --log-dir when launching the debug adapter. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; - let ptvsdPathToUse: string; - try { - ptvsdPathToUse = await this.getPtvsdFolder(pythonPath); - } catch { - ptvsdPathToUse = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); - } - return new DebugAdapterExecutable(`${pythonPath}`, [path.join(ptvsdPathToUse, 'ptvsd', 'adapter'), ...logArgs]); + const ptvsdPathToUse = await this.getPtvsdPath(pythonPath); + return new DebugAdapterExecutable(`${pythonPath}`, [ptvsdPathToUse, ...logArgs]); } // Use the Node debug adapter (and ptvsd_launcher.py) @@ -54,6 +51,38 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac throw new Error('Debug Adapter Executable not provided'); } + /** + * Check and return whether the user is in the PTVSD wheels experiment or not. + * + * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) + * @returns {Promise} Whether the user is in the experiment or not. + * @memberof DebugAdapterDescriptorFactory + */ + public async useNewPtvsd(pythonPath: string): Promise { + if (!this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { + return false; + } + + const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); + if (!interpreterInfo || !interpreterInfo.version || !interpreterInfo.version.raw.startsWith('3.7')) { + return false; + } + + return true; + } + + public async getPtvsdPath(pythonPath: string): Promise { + let ptvsdPathToUse: string; + + try { + ptvsdPathToUse = await this.getPtvsdFolder(pythonPath); + } catch { + ptvsdPathToUse = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); + } + + return path.join(ptvsdPathToUse, 'ptvsd', 'adapter'); + } + /** * Get the python executable used to launch the Python Debug Adapter. * In the case of `attach` scenarios, just use the workspace interpreter, else first available one. @@ -126,7 +155,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac return cachedPath; } catch (err) { // The path to the cached folder didn't exist (ptvsd requirements updated during development), so run the script again. - } + } } // The ptvsd path wasn't cached, so run the script and cache it. @@ -139,24 +168,4 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac return pathToPtvsd; } - - /** - * Check and return whether the user is in the PTVSD wheels experiment or not. - * - * @param {string} pythonPath Path to the python executable used to launch the Python Debug Adapter (result of `this.getPythonPath()`) - * @returns {Promise} Whether the user is in the experiment or not. - * @memberof DebugAdapterDescriptorFactory - */ - private async useNewPtvsd(pythonPath: string): Promise { - if (!this.experimentsManager.inExperiment(DebugAdapterNewPtvsd.experiment)) { - return false; - } - - const interpreterInfo = await this.interpreterService.getInterpreterDetails(pythonPath); - if (!interpreterInfo || !interpreterInfo.version || !interpreterInfo.version.raw.startsWith('3.7')) { - return false; - } - - return true; - } } diff --git a/src/client/extension.ts b/src/client/extension.ts index cdaac6adf599..fc72069fa0e8 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,6 +41,7 @@ import { DebugService } from './common/application/debugService'; import { IApplicationShell, ICommandManager, IWorkspaceService } from './common/application/types'; import { Commands, isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as registerDotNetTypes } from './common/dotnet/serviceRegistry'; +import { DebugAdapterDescriptorFactory as DebugAdapterExperiment } from './common/experimentGroups'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { traceError } from './common/logger'; import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; @@ -66,10 +67,11 @@ import { registerTypes as variableRegisterTypes } from './common/variables/servi import { registerTypes as dataScienceRegisterTypes } from './datascience/serviceRegistry'; import { IDataScience, IDebugLocationTrackerFactory } from './datascience/types'; import { DebuggerTypeName } from './debugger/constants'; +import { DebugAdapterDescriptorFactory } from './debugger/extension/adapter/factory'; import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; -import { IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; +import { IDebugAdapterDescriptorFactory, IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { AutoSelectionRule, IInterpreterAutoSelectionRule, IInterpreterAutoSelectionService } from './interpreter/autoSelection/types'; import { IInterpreterSelector } from './interpreter/configuration/types'; @@ -197,7 +199,17 @@ async function activateUnsafe(context: ExtensionContext): Promise durations.endActivateTime = stopWatch.elapsedTime; activationDeferred.resolve(); - const api = buildApi(Promise.all([activationDeferred.promise, activationPromise])); + const experimentsManager = serviceContainer.get(IExperimentsManager); + let newPtvsdPath: string | undefined; + + if (experimentsManager.inExperiment(DebugAdapterExperiment.experiment)) { + const debugAdapterDescriptor = serviceContainer.get(IDebugAdapterDescriptorFactory) as DebugAdapterDescriptorFactory; + if (debugAdapterDescriptor.useNewPtvsd(pythonSettings.pythonPath)) { + newPtvsdPath = await debugAdapterDescriptor.getPtvsdPath(pythonSettings.pythonPath); + } + } + + const api = buildApi(Promise.all([activationDeferred.promise, activationPromise]), newPtvsdPath); // In test environment return the DI Container. if (isTestExecution()) { // tslint:disable:no-any From a0f3995241b77dd38cb1ff960fb61b0853c7d0db Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Fri, 20 Sep 2019 15:02:27 -0700 Subject: [PATCH 42/45] Fix tests --- src/test/debugger/extension/adapter/factory.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 de90d6e60f12..738a0d808b61 100644 --- a/src/test/debugger/extension/adapter/factory.unit.test.ts +++ b/src/test/debugger/extension/adapter/factory.unit.test.ts @@ -42,7 +42,7 @@ suite('Debugging - Adapter Factory', () => { const nodeExecutable = { command: 'node', args: [] }; const mockExtensionVersion = new SemVer('2019.9.0'); - const ptvsdPath = path.join('path', 'to', 'ptvsd'); + const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const pythonPath = path.join('path', 'to', 'python', 'interpreter'); const interpreter = { architecture: Architecture.Unknown, From abab26f50ee038af869d17e3b4b6db084e1dbb0b Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 23 Sep 2019 10:31:27 -0700 Subject: [PATCH 43/45] Revert remote debugging changes --- src/client/api.ts | 4 ++-- src/client/extension.ts | 16 ++-------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/client/api.ts b/src/client/api.ts index ce71b1770825..177dc997397d 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -33,7 +33,7 @@ export interface IExtensionApi { } // tslint:disable-next-line:no-any -export function buildApi(ready: Promise, ptvsdWheelsScriptPath?: string) { +export function buildApi(ready: Promise) { return { // 'ready' will propogate the exception, but we must log it here first. ready: ready.catch((ex) => { @@ -42,7 +42,7 @@ export function buildApi(ready: Promise, ptvsdWheelsScriptPath?: string) { }), debug: { async getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean = true): Promise { - return new RemoteDebuggerExternalLauncherScriptProvider(ptvsdWheelsScriptPath).getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); + return new RemoteDebuggerExternalLauncherScriptProvider().getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); } } }; diff --git a/src/client/extension.ts b/src/client/extension.ts index fc72069fa0e8..cdaac6adf599 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,7 +41,6 @@ import { DebugService } from './common/application/debugService'; import { IApplicationShell, ICommandManager, IWorkspaceService } from './common/application/types'; import { Commands, isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as registerDotNetTypes } from './common/dotnet/serviceRegistry'; -import { DebugAdapterDescriptorFactory as DebugAdapterExperiment } from './common/experimentGroups'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { traceError } from './common/logger'; import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; @@ -67,11 +66,10 @@ import { registerTypes as variableRegisterTypes } from './common/variables/servi import { registerTypes as dataScienceRegisterTypes } from './datascience/serviceRegistry'; import { IDataScience, IDebugLocationTrackerFactory } from './datascience/types'; import { DebuggerTypeName } from './debugger/constants'; -import { DebugAdapterDescriptorFactory } from './debugger/extension/adapter/factory'; import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; -import { IDebugAdapterDescriptorFactory, IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; +import { IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { AutoSelectionRule, IInterpreterAutoSelectionRule, IInterpreterAutoSelectionService } from './interpreter/autoSelection/types'; import { IInterpreterSelector } from './interpreter/configuration/types'; @@ -199,17 +197,7 @@ async function activateUnsafe(context: ExtensionContext): Promise durations.endActivateTime = stopWatch.elapsedTime; activationDeferred.resolve(); - const experimentsManager = serviceContainer.get(IExperimentsManager); - let newPtvsdPath: string | undefined; - - if (experimentsManager.inExperiment(DebugAdapterExperiment.experiment)) { - const debugAdapterDescriptor = serviceContainer.get(IDebugAdapterDescriptorFactory) as DebugAdapterDescriptorFactory; - if (debugAdapterDescriptor.useNewPtvsd(pythonSettings.pythonPath)) { - newPtvsdPath = await debugAdapterDescriptor.getPtvsdPath(pythonSettings.pythonPath); - } - } - - const api = buildApi(Promise.all([activationDeferred.promise, activationPromise]), newPtvsdPath); + const api = buildApi(Promise.all([activationDeferred.promise, activationPromise])); // In test environment return the DI Container. if (isTestExecution()) { // tslint:disable:no-any From 93f838d0bd41a8df3da949f65022a2319ccf7821 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 23 Sep 2019 10:44:37 -0700 Subject: [PATCH 44/45] Add todo comment --- src/client/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/api.ts b/src/client/api.ts index 177dc997397d..8f9dfd99b8d2 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -41,6 +41,7 @@ export function buildApi(ready: Promise) { return Promise.reject(ex); }), debug: { + // TODO add support for ptvsd wheels experiment, see https://github.com/microsoft/vscode-python/issues/7549 async getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean = true): Promise { return new RemoteDebuggerExternalLauncherScriptProvider().getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); } From 2e51f22bbd3498cf7f30c5de8b1ebc67694b420f Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel Date: Mon, 23 Sep 2019 11:06:20 -0700 Subject: [PATCH 45/45] Disable linting for the todo --- src/client/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/api.ts b/src/client/api.ts index 8f9dfd99b8d2..511808c939c7 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -41,7 +41,8 @@ export function buildApi(ready: Promise) { return Promise.reject(ex); }), debug: { - // TODO add support for ptvsd wheels experiment, see https://github.com/microsoft/vscode-python/issues/7549 + // tslint:disable-next-line:no-suspicious-comment + // TODO: Add support for ptvsd wheels experiment, see https://github.com/microsoft/vscode-python/issues/7549 async getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean = true): Promise { return new RemoteDebuggerExternalLauncherScriptProvider().getLauncherArgs({ host, port, waitUntilDebuggerAttaches }); }