Skip to content
5 changes: 4 additions & 1 deletion src/extension/common/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,8 @@ export function fileToCommandArgumentForPythonExt(source: string): string {
if (!source) {
return source;
}
return toCommandArgumentForPythonExt(source).replace(/\\/g, '/');

let result = toCommandArgumentForPythonExt(source);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the correct change. On non windows machines it may have the wrong path separators.

The unit tests are failing only because the old code did the wrong thing. The unit tests are wrong. They need to be platform specific too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok, I excluded that because I suggested amending previously pushed tests but we discarded that. Let me re-push it once more

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay this leaves the 'bug' that was there before. It should be checking OS.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, restored the OS-checking path as you recommended before.

return result.replace(/\\/g, '/');
}
8 changes: 7 additions & 1 deletion src/extension/debugger/adapter/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,20 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac

let executable = command.shift() ?? 'python';

// Always ensure interpreter/command is quoted if necessary. Previously this was
// only done in the debugAdapterPath branch which meant that in the common case
// (using the built‑in adapter path) an interpreter path containing spaces would
// be passed unquoted, resulting in a fork/spawn failure on Windows. See bug
// report for details.
executable = fileToCommandArgumentForPythonExt(executable);
Comment thread
fgiancane8 marked this conversation as resolved.

// "logToFile" is not handled directly by the adapter - instead, we need to pass
// the corresponding CLI switch when spawning it.
const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : [];

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);
}

Expand Down
18 changes: 17 additions & 1 deletion src/test/unittest/adapter/factory.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,23 @@ suite('Debugging - Adapter Factory', () => {

assert.deepStrictEqual(descriptor, debugExecutable);
});
test('Add quotes to interpreter path with spaces', async () => {
test('Add quotes to interpreter path with spaces (default adapter path)', async () => {
const session = createSession({});
const interpreterPathSpaces = 'path/to/python interpreter with spaces';
const interpreterPathSpacesQuoted = `"${interpreterPathSpaces}"`;
const debugExecutable = new DebugAdapterExecutable(interpreterPathSpacesQuoted, [debugAdapterPath]);

getInterpreterDetailsStub.resolves({ path: [interpreterPathSpaces] });
const interpreterSpacePath: PythonEnvironment = createInterpreter(interpreterPathSpaces, '3.7.4-test');
// Add architecture for completeness.
(interpreterSpacePath as any).architecture = Architecture.Unknown;
resolveEnvironmentStub.withArgs(interpreterPathSpaces).resolves(interpreterSpacePath);
const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable);

assert.deepStrictEqual(descriptor, debugExecutable);
});

test('Add quotes to interpreter path with spaces when debugAdapterPath is specified', async () => {
const customAdapterPath = 'custom/debug/adapter/customAdapterPath';
const session = createSession({ debugAdapterPath: customAdapterPath });
const interpreterPathSpaces = 'path/to/python interpreter with spaces';
Expand Down
3 changes: 2 additions & 1 deletion src/test/unittest/adapter/remoteLaunchers.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../../../extension/common/constants';
import '../../../extension/common/promiseUtils';
import * as launchers from '../../../extension/debugger/adapter/remoteLaunchers';
import { fileToCommandArgumentForPythonExt } from '../../../extension/common/stringUtils';

suite('External debugpy Debugger Launcher', () => {
[
Expand All @@ -18,7 +19,7 @@ suite('External debugpy Debugger Launcher', () => {
},
{
testName: 'When path to debugpy contains spaces',
path: path.join('path', 'to', 'debugpy', 'with spaces'),
path: fileToCommandArgumentForPythonExt(path.join('path', 'to', 'debugpy', 'with spaces')),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps something like this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel like this is the right way to test this as you're reusing the internal function to test itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought the same, although in the end we want just to validate that the "normalised" input is equivalent. What if we replace(//, ) the input here, simulating the same behaviour, without exposing the internals?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all, it seems to me that the platform-specific behaviour is in path.join

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you're seeing but my expectation is that fileToCommandArgumentForPythonExt will generate platform specific output. Or at least not change the input path's separator.

The unit tests should be checking that quotes are around a path, they don't really care about the separator in the paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you're seeing but my expectation is that fileToCommandArgumentForPythonExt will generate platform specific output. Or at least not change the input path's separator.

The unit tests should be checking that quotes are around a path, they don't really care about the separator in the paths.

So yeah tests data is encoded as "path/to/something/with space" but of course Windows would generate "path\to\something\with spaces".

I kept the sane logic in the source code and updated the test code to use platform-specific data.

expectedPath: '"path/to/debugpy/with spaces"',
},
].forEach((testParams) => {
Expand Down