From b435ab3f7081032811d67bfeb6c76e32b0ee1899 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:38:52 +0000 Subject: [PATCH 1/4] Initial plan From f46d9d1e97fdfff6c51908862ea377634cec0d68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:48:39 +0000 Subject: [PATCH 2/4] Add comprehensive tests for resolveAndUpdatePythonPath prioritization logic Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> --- .../configuration/resolvers/base.unit.test.ts | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) diff --git a/src/test/unittest/configuration/resolvers/base.unit.test.ts b/src/test/unittest/configuration/resolvers/base.unit.test.ts index 14765f6d..ee1e0c81 100644 --- a/src/test/unittest/configuration/resolvers/base.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/base.unit.test.ts @@ -270,6 +270,356 @@ suite('Debugging - Config Resolver', () => { expect(config).to.have.property('debugLauncherPython', pythonPath); }); + // Tests for prioritization of python path configuration + suite('resolveAndUpdatePythonPath prioritization tests', () => { + test('When pythonPath is a concrete path and python is undefined, python should be set to pythonPath value', async () => { + const expectedPath = path.join('path', 'to', 'custom', 'python'); + const config = { + pythonPath: expectedPath, + python: undefined, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', expectedPath); + }); + + test('When pythonPath is a concrete path and python is a different concrete path, python should take precedence', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const pythonValue = path.join('path', 'to', 'python', 'python'); + const config = { + pythonPath: pythonPathValue, + python: pythonValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + }); + + test('When pythonPath is ${command:python.interpreterPath} and python is a concrete path, python should take precedence', async () => { + const pythonValue = path.join('path', 'to', 'python', 'python'); + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + python: pythonValue, + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + }); + + test('When pythonPath is a concrete path and python is ${command:python.interpreterPath}, python should resolve from interpreter', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: pythonPathValue, + python: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + }); + + test('When both pythonPath and python are ${command:python.interpreterPath}, both should resolve to interpreter path', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + python: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + }); + + test('When pythonPath is not set and python is not set, both should resolve from interpreter', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = {}; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + }); + + test('debugAdapterPython should use pythonPath when neither debugAdapterPython nor python are set', async () => { + const pythonPathValue = path.join('path', 'to', 'custom', 'python'); + const config = { + pythonPath: pythonPathValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonPathValue); + expect(config).to.have.property('debugAdapterPython', pythonPathValue); + }); + + test('debugAdapterPython should use python when pythonPath is set but python has different value', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const pythonValue = path.join('path', 'to', 'python', 'python'); + const config = { + pythonPath: pythonPathValue, + python: pythonValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + expect(config).to.have.property('debugAdapterPython', pythonValue); + }); + + test('debugAdapterPython should prefer explicitly set debugAdapterPython over pythonPath', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); + const config = { + pythonPath: pythonPathValue, + debugAdapterPython: debugAdapterValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonPathValue); + expect(config).to.have.property('debugAdapterPython', debugAdapterValue); + }); + + test('debugLauncherPython should use pythonPath when neither debugLauncherPython nor python are set', async () => { + const pythonPathValue = path.join('path', 'to', 'custom', 'python'); + const config = { + pythonPath: pythonPathValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonPathValue); + expect(config).to.have.property('debugLauncherPython', pythonPathValue); + }); + + test('debugLauncherPython should use python when pythonPath is set but python has different value', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const pythonValue = path.join('path', 'to', 'python', 'python'); + const config = { + pythonPath: pythonPathValue, + python: pythonValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + expect(config).to.have.property('debugLauncherPython', pythonValue); + }); + + test('debugLauncherPython should prefer explicitly set debugLauncherPython over pythonPath', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); + const config = { + pythonPath: pythonPathValue, + debugLauncherPython: debugLauncherValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonPathValue); + expect(config).to.have.property('debugLauncherPython', debugLauncherValue); + }); + + test('All three debug python fields can have different values when explicitly set', async () => { + const pythonValue = path.join('path', 'to', 'python', 'python'); + const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); + const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); + const config = { + python: pythonValue, + debugAdapterPython: debugAdapterValue, + debugLauncherPython: debugLauncherValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + expect(config).to.have.property('debugAdapterPython', debugAdapterValue); + expect(config).to.have.property('debugLauncherPython', debugLauncherValue); + }); + + test('When debugAdapterPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + debugAdapterPython: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + expect(config).to.have.property('debugAdapterPython', interpreterPath); + }); + + test('When debugLauncherPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + debugLauncherPython: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + expect(config).to.have.property('debugLauncherPython', interpreterPath); + }); + + test('Complex scenario: pythonPath set, python differs, debugAdapterPython and debugLauncherPython both set differently', async () => { + const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); + const pythonValue = path.join('path', 'to', 'python', 'python'); + const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); + const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); + const config = { + pythonPath: pythonPathValue, + python: pythonValue, + debugAdapterPython: debugAdapterValue, + debugLauncherPython: debugLauncherValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + expect(config).to.have.property('debugAdapterPython', debugAdapterValue); + expect(config).to.have.property('debugLauncherPython', debugLauncherValue); + }); + + test('When pythonPath is undefined and python is concrete path, debugAdapter and debugLauncher should use python', async () => { + const pythonValue = path.join('path', 'to', 'python', 'python'); + const config = { + python: pythonValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', pythonValue); + expect(config).to.have.property('debugAdapterPython', pythonValue); + expect(config).to.have.property('debugLauncherPython', pythonValue); + }); + + test('When pythonPath is empty string, it should be treated as not set and resolve from interpreter', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(config).to.not.have.property('pythonPath'); + expect(config).to.have.property('python', interpreterPath); + }); + + // Tests for pythonPathSource field + test('pythonPathSource should be settingsJson when python is ${command:python.interpreterPath}', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + python: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); + }); + + test('pythonPathSource should be settingsJson when python is undefined', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: interpreterPath, + python: undefined, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); + }); + + test('pythonPathSource should be launchJson when python is explicitly set to a concrete path', async () => { + const pythonValue = path.join('path', 'to', 'python', 'python'); + const config = { + python: pythonValue, + }; + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.launchJson); + }); + + test('pythonPathSource should be launchJson when python is a concrete path even if pythonPath is ${command:python.interpreterPath}', async () => { + const pythonValue = path.join('path', 'to', 'python', 'python'); + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + python: pythonValue, + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.launchJson); + }); + + test('pythonPathSource should be settingsJson when both pythonPath and python are ${command:python.interpreterPath}', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = { + pythonPath: '${command:python.interpreterPath}', + python: '${command:python.interpreterPath}', + }; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); + }); + + test('pythonPathSource should be settingsJson when neither pythonPath nor python are set', async () => { + const interpreterPath = path.join('path', 'from', 'interpreter'); + const config = {}; + + getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); + + await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); + + expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); + }); + }); + const localHostTestMatrix: Record = { localhost: true, '127.0.0.1': true, From 93bf1fc75b66b6ee1b4e5a33370aa718775c61d8 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:45:37 -0700 Subject: [PATCH 3/4] add tests for resolveAndUpdatePythonPath --- .../configuration/resolvers/base.unit.test.ts | 350 ------------------ 1 file changed, 350 deletions(-) diff --git a/src/test/unittest/configuration/resolvers/base.unit.test.ts b/src/test/unittest/configuration/resolvers/base.unit.test.ts index ee1e0c81..14765f6d 100644 --- a/src/test/unittest/configuration/resolvers/base.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/base.unit.test.ts @@ -270,356 +270,6 @@ suite('Debugging - Config Resolver', () => { expect(config).to.have.property('debugLauncherPython', pythonPath); }); - // Tests for prioritization of python path configuration - suite('resolveAndUpdatePythonPath prioritization tests', () => { - test('When pythonPath is a concrete path and python is undefined, python should be set to pythonPath value', async () => { - const expectedPath = path.join('path', 'to', 'custom', 'python'); - const config = { - pythonPath: expectedPath, - python: undefined, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', expectedPath); - }); - - test('When pythonPath is a concrete path and python is a different concrete path, python should take precedence', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const pythonValue = path.join('path', 'to', 'python', 'python'); - const config = { - pythonPath: pythonPathValue, - python: pythonValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - }); - - test('When pythonPath is ${command:python.interpreterPath} and python is a concrete path, python should take precedence', async () => { - const pythonValue = path.join('path', 'to', 'python', 'python'); - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - python: pythonValue, - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - }); - - test('When pythonPath is a concrete path and python is ${command:python.interpreterPath}, python should resolve from interpreter', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: pythonPathValue, - python: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - }); - - test('When both pythonPath and python are ${command:python.interpreterPath}, both should resolve to interpreter path', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - python: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - }); - - test('When pythonPath is not set and python is not set, both should resolve from interpreter', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = {}; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - }); - - test('debugAdapterPython should use pythonPath when neither debugAdapterPython nor python are set', async () => { - const pythonPathValue = path.join('path', 'to', 'custom', 'python'); - const config = { - pythonPath: pythonPathValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonPathValue); - expect(config).to.have.property('debugAdapterPython', pythonPathValue); - }); - - test('debugAdapterPython should use python when pythonPath is set but python has different value', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const pythonValue = path.join('path', 'to', 'python', 'python'); - const config = { - pythonPath: pythonPathValue, - python: pythonValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - expect(config).to.have.property('debugAdapterPython', pythonValue); - }); - - test('debugAdapterPython should prefer explicitly set debugAdapterPython over pythonPath', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); - const config = { - pythonPath: pythonPathValue, - debugAdapterPython: debugAdapterValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonPathValue); - expect(config).to.have.property('debugAdapterPython', debugAdapterValue); - }); - - test('debugLauncherPython should use pythonPath when neither debugLauncherPython nor python are set', async () => { - const pythonPathValue = path.join('path', 'to', 'custom', 'python'); - const config = { - pythonPath: pythonPathValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonPathValue); - expect(config).to.have.property('debugLauncherPython', pythonPathValue); - }); - - test('debugLauncherPython should use python when pythonPath is set but python has different value', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const pythonValue = path.join('path', 'to', 'python', 'python'); - const config = { - pythonPath: pythonPathValue, - python: pythonValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - expect(config).to.have.property('debugLauncherPython', pythonValue); - }); - - test('debugLauncherPython should prefer explicitly set debugLauncherPython over pythonPath', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); - const config = { - pythonPath: pythonPathValue, - debugLauncherPython: debugLauncherValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonPathValue); - expect(config).to.have.property('debugLauncherPython', debugLauncherValue); - }); - - test('All three debug python fields can have different values when explicitly set', async () => { - const pythonValue = path.join('path', 'to', 'python', 'python'); - const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); - const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); - const config = { - python: pythonValue, - debugAdapterPython: debugAdapterValue, - debugLauncherPython: debugLauncherValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - expect(config).to.have.property('debugAdapterPython', debugAdapterValue); - expect(config).to.have.property('debugLauncherPython', debugLauncherValue); - }); - - test('When debugAdapterPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - debugAdapterPython: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - expect(config).to.have.property('debugAdapterPython', interpreterPath); - }); - - test('When debugLauncherPython is ${command:python.interpreterPath}, it should fallback to resolved pythonPath', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - debugLauncherPython: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - expect(config).to.have.property('debugLauncherPython', interpreterPath); - }); - - test('Complex scenario: pythonPath set, python differs, debugAdapterPython and debugLauncherPython both set differently', async () => { - const pythonPathValue = path.join('path', 'to', 'pythonPath', 'python'); - const pythonValue = path.join('path', 'to', 'python', 'python'); - const debugAdapterValue = path.join('path', 'to', 'debugAdapter', 'python'); - const debugLauncherValue = path.join('path', 'to', 'debugLauncher', 'python'); - const config = { - pythonPath: pythonPathValue, - python: pythonValue, - debugAdapterPython: debugAdapterValue, - debugLauncherPython: debugLauncherValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - expect(config).to.have.property('debugAdapterPython', debugAdapterValue); - expect(config).to.have.property('debugLauncherPython', debugLauncherValue); - }); - - test('When pythonPath is undefined and python is concrete path, debugAdapter and debugLauncher should use python', async () => { - const pythonValue = path.join('path', 'to', 'python', 'python'); - const config = { - python: pythonValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', pythonValue); - expect(config).to.have.property('debugAdapterPython', pythonValue); - expect(config).to.have.property('debugLauncherPython', pythonValue); - }); - - test('When pythonPath is empty string, it should be treated as not set and resolve from interpreter', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(config).to.not.have.property('pythonPath'); - expect(config).to.have.property('python', interpreterPath); - }); - - // Tests for pythonPathSource field - test('pythonPathSource should be settingsJson when python is ${command:python.interpreterPath}', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - python: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); - }); - - test('pythonPathSource should be settingsJson when python is undefined', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: interpreterPath, - python: undefined, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); - }); - - test('pythonPathSource should be launchJson when python is explicitly set to a concrete path', async () => { - const pythonValue = path.join('path', 'to', 'python', 'python'); - const config = { - python: pythonValue, - }; - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.launchJson); - }); - - test('pythonPathSource should be launchJson when python is a concrete path even if pythonPath is ${command:python.interpreterPath}', async () => { - const pythonValue = path.join('path', 'to', 'python', 'python'); - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - python: pythonValue, - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.launchJson); - }); - - test('pythonPathSource should be settingsJson when both pythonPath and python are ${command:python.interpreterPath}', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = { - pythonPath: '${command:python.interpreterPath}', - python: '${command:python.interpreterPath}', - }; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); - }); - - test('pythonPathSource should be settingsJson when neither pythonPath nor python are set', async () => { - const interpreterPath = path.join('path', 'from', 'interpreter'); - const config = {}; - - getInterpreterDetailsStub.resolves({ path: [interpreterPath] } as unknown as PythonEnvironment); - - await resolver.resolveAndUpdatePythonPath(undefined, config as LaunchRequestArguments); - - expect(resolver.getPythonPathSource()).to.equal(PythonPathSource.settingsJson); - }); - }); - const localHostTestMatrix: Record = { localhost: true, '127.0.0.1': true, From 91d23cd81ec1417d861b4a69c4a9ac9164054ab5 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:00:08 -0700 Subject: [PATCH 4/4] remove deprecated pythonPath config ref --- src/extension/debugger/adapter/factory.ts | 2 - .../debugger/configuration/resolvers/base.ts | 48 ++++--------------- .../configuration/resolvers/launch.ts | 11 ----- 3 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/extension/debugger/adapter/factory.ts b/src/extension/debugger/adapter/factory.ts index aa83b3bd..dc3773b0 100644 --- a/src/extension/debugger/adapter/factory.ts +++ b/src/extension/debugger/adapter/factory.ts @@ -116,8 +116,6 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac ): Promise { if (configuration.debugAdapterPython !== undefined) { return this.getExecutableCommand(await resolveEnvironment(configuration.debugAdapterPython)); - } else if (configuration.pythonPath) { - return this.getExecutableCommand(await resolveEnvironment(configuration.pythonPath)); } const resourceUri = workspaceFolder ? workspaceFolder.uri : undefined; diff --git a/src/extension/debugger/configuration/resolvers/base.ts b/src/extension/debugger/configuration/resolvers/base.ts index 8b887107..a34a0ff5 100644 --- a/src/extension/debugger/configuration/resolvers/base.ts +++ b/src/extension/debugger/configuration/resolvers/base.ts @@ -125,7 +125,7 @@ export abstract class BaseConfigurationResolver } /** - * Resolves Python interpreter paths and handles the legacy pythonPath deprecation. + * Resolves Python interpreter paths. * * @param workspaceFolder The workspace folder URI for variable resolution and interpreter detection * @param debugConfiguration The launch configuration to update with resolved Python paths @@ -145,7 +145,6 @@ export abstract class BaseConfigurationResolver traceLog( `resolveAndUpdatePythonPath - Initial state: ` + - `pythonPath='${debugConfiguration.pythonPath}', ` + `python='${debugConfiguration.python}', ` + `debugAdapterPython='${debugConfiguration.debugAdapterPython}', ` + `debugLauncherPython='${debugConfiguration.debugLauncherPython}', ` + @@ -153,29 +152,11 @@ export abstract class BaseConfigurationResolver `resolvedInterpreterPath='${resolvedInterpreterPath}'`, ); - // STEP 1: Resolve legacy pythonPath property (DEPRECATED) - // pythonPath will equal user set value, or getInterpreterDetails if undefined or set to command - if (debugConfiguration.pythonPath === '${command:python.interpreterPath}' || !debugConfiguration.pythonPath) { - this.pythonPathSource = PythonPathSource.settingsJson; - debugConfiguration.pythonPath = resolvedInterpreterPath; - } else { - // User provided explicit pythonPath in launch.json - debugConfiguration.pythonPath = resolveWorkspaceVariables( - debugConfiguration.pythonPath, - workspaceFolder?.fsPath, - undefined, - ); - } - - // STEP 2: Resolve current python property (CURRENT STANDARD) - if (debugConfiguration.python === '${command:python.interpreterPath}') { - // if python is set to the command, resolve it + // Resolve current python property + if (debugConfiguration.python === '${command:python.interpreterPath}' || !debugConfiguration.python) { + // if python is set to the command or undefined, resolve it this.pythonPathSource = PythonPathSource.settingsJson; debugConfiguration.python = resolvedInterpreterPath; - } else if (!debugConfiguration.python) { - // fallback to pythonPath if python undefined - this.pythonPathSource = PythonPathSource.settingsJson; - debugConfiguration.python = debugConfiguration.pythonPath; } else { // User provided explicit python path in launch.json this.pythonPathSource = PythonPathSource.launchJson; @@ -186,16 +167,12 @@ export abstract class BaseConfigurationResolver ); } - // STEP 3: Set debug adapter and launcher Python paths (backwards compatible) + // Set debug adapter and launcher Python paths this.setDebugComponentPythonPaths(debugConfiguration); - - // STEP 4: Clean up - remove the deprecated pythonPath property - delete debugConfiguration.pythonPath; } /** - * Sets debugAdapterPython and debugLauncherPython with backwards compatibility. - * Prefers pythonPath over python for these internal properties. + * Sets debugAdapterPython and debugLauncherPython. * * @param debugConfiguration The debug configuration to update */ @@ -208,20 +185,11 @@ export abstract class BaseConfigurationResolver debugConfiguration.debugLauncherPython === '${command:python.interpreterPath}' || debugConfiguration.debugLauncherPython === undefined; - // Default fallback path (prefer pythonPath for backwards compatibility) - const fallbackPath = debugConfiguration.pythonPath ?? debugConfiguration.python; - - if (debugConfiguration.pythonPath !== debugConfiguration.python) { - sendTelemetryEvent(EventName.DEPRECATED_CODE_PATH_USAGE, undefined, { - codePath: 'different_python_paths_in_debug_config', - }); - } - if (shouldSetDebugAdapter) { - debugConfiguration.debugAdapterPython = fallbackPath; + debugConfiguration.debugAdapterPython = debugConfiguration.python; } if (shouldSetDebugLauncher) { - debugConfiguration.debugLauncherPython = fallbackPath; + debugConfiguration.debugLauncherPython = debugConfiguration.python; } } diff --git a/src/extension/debugger/configuration/resolvers/launch.ts b/src/extension/debugger/configuration/resolvers/launch.ts index 89efe76a..8508e79c 100644 --- a/src/extension/debugger/configuration/resolvers/launch.ts +++ b/src/extension/debugger/configuration/resolvers/launch.ts @@ -68,17 +68,6 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { - if (debugConfiguration.python === undefined) { - debugConfiguration.python = debugConfiguration.pythonPath; - } - if (debugConfiguration.debugAdapterPython === undefined) { - debugConfiguration.debugAdapterPython = debugConfiguration.pythonPath; - } - if (debugConfiguration.debugLauncherPython === undefined) { - debugConfiguration.debugLauncherPython = debugConfiguration.pythonPath; - } - delete debugConfiguration.pythonPath; - if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; }