diff --git a/src/extension/debugger/adapter/factory.ts b/src/extension/debugger/adapter/factory.ts index 16bb50e0..114e89c1 100644 --- a/src/extension/debugger/adapter/factory.ts +++ b/src/extension/debugger/adapter/factory.ts @@ -25,6 +25,7 @@ import { Commands, EXTENSION_ROOT_DIR } from '../../common/constants'; import { Common, DebugConfigStrings, Interpreters } from '../../common/utils/localize'; import { IPersistentStateFactory } from '../../common/types'; import { ResolvedEnvironment } from '@vscode/python-extension'; +import { fileToCommandArgumentForPythonExt } from '../../common/stringUtils'; // persistent state names, exported to make use of in testing export enum debugStateKeys { @@ -76,7 +77,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac sendTelemetryEvent(EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS); } - const executable = command.shift() ?? 'python'; + let executable = command.shift() ?? 'python'; // "logToFile" is not handled directly by the adapter - instead, we need to pass // the corresponding CLI switch when spawning it. @@ -85,6 +86,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (configuration.debugAdapterPath !== undefined) { const args = command.concat([configuration.debugAdapterPath, ...logArgs]); traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`); + executable = fileToCommandArgumentForPythonExt(executable); return new DebugAdapterExecutable(executable, args); } diff --git a/src/test/unittest/adapter/factory.unit.test.ts b/src/test/unittest/adapter/factory.unit.test.ts index 78a5e7a6..86342d2c 100644 --- a/src/test/unittest/adapter/factory.unit.test.ts +++ b/src/test/unittest/adapter/factory.unit.test.ts @@ -41,7 +41,7 @@ suite('Debugging - Adapter Factory', () => { const nodeExecutable = undefined; const debugAdapterPath = path.join(EXTENSION_ROOT_DIR, 'bundled', 'libs', 'debugpy', 'adapter'); - const pythonPath = path.join('path', 'to', 'python', 'interpreter'); + const pythonPath = 'path/to/python/interpreter'; const interpreter = { architecture: Architecture.Unknown, path: pythonPath, @@ -291,6 +291,27 @@ suite('Debugging - Adapter Factory', () => { assert.deepStrictEqual(descriptor, debugExecutable); }); + test('Add quotes to interpreter path with spaces', async () => { + const customAdapterPath = 'custom/debug/adapter/customAdapterPath'; + const session = createSession({ debugAdapterPath: customAdapterPath }); + const interpreterPathSpaces = 'path/to/python interpreter with spaces'; + const interpreterPathSpacesQuoted = `"${interpreterPathSpaces}"`; + const debugExecutable = new DebugAdapterExecutable(interpreterPathSpacesQuoted, [customAdapterPath]); + + getInterpreterDetailsStub.resolves({ path: [interpreterPathSpaces] }); + const interpreterSpacePath = { + architecture: Architecture.Unknown, + path: interpreterPathSpaces, + sysPrefix: '', + sysVersion: '', + envType: 'Unknow', + version: new SemVer('3.7.4-test'), + }; + resolveEnvironmentStub.withArgs(interpreterPathSpaces).resolves(interpreterSpacePath); + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepStrictEqual(descriptor, debugExecutable); + }); test('Use "debugAdapterPython" when specified', async () => { const session = createSession({ debugAdapterPython: '/bin/custompy' });