diff --git a/bundled/scripts/noConfigScripts/debugpy b/bundled/scripts/noConfigScripts/debugpy index 1893a2cd..def62ec5 100755 --- a/bundled/scripts/noConfigScripts/debugpy +++ b/bundled/scripts/noConfigScripts/debugpy @@ -1,3 +1,4 @@ #! /bin/bash # Bash script +export DEBUGPY_ADAPTER_ENDPOINTS=$VSCODE_DEBUGPY_ADAPTER_ENDPOINTS python3 $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@ diff --git a/bundled/scripts/noConfigScripts/debugpy.ps1 b/bundled/scripts/noConfigScripts/debugpy.ps1 index 945c5091..bf949d64 100755 --- a/bundled/scripts/noConfigScripts/debugpy.ps1 +++ b/bundled/scripts/noConfigScripts/debugpy.ps1 @@ -1,6 +1,7 @@ # PowerShell script -if ($PSVersionTable.OS -match "Windows") { +$os = [System.Environment]::OSVersion.Platform +if ($os -eq [System.PlatformID]::Win32NT) { python $env:BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $args } else { python3 $env:BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $args -} +} \ No newline at end of file diff --git a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts index e2cad8b2..ac906613 100644 --- a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts +++ b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts @@ -12,19 +12,18 @@ export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): traceLog('Getting configurations for workspace'); const filename = path.join(workspace.uri.fsPath, '.vscode', 'launch.json'); if (!(await fs.pathExists(filename))) { - // Check launch config in the workspace file - const codeWorkspaceConfig = getConfiguration('launch', workspace); - if (!codeWorkspaceConfig.configurations || !Array.isArray(codeWorkspaceConfig.configurations)) { - return []; - } - traceLog('Using configuration in workspace'); - return codeWorkspaceConfig.configurations; + return getConfigurationsFromSettings(workspace); } - const text = await fs.readFile(filename, 'utf-8'); const parsed = parse(text, [], { allowTrailingComma: true, disallowComments: false }); - if (!parsed.configurations || !Array.isArray(parsed.configurations)) { - throw Error('Missing field in launch.json: configurations'); + // no launch.json or no configurations found in launch.json, look in settings.json + if (!parsed || !parsed.configurations) { + traceLog('No configurations found in launch.json, looking in settings.json.'); + return getConfigurationsFromSettings(workspace); + } + // configurations found in launch.json, verify them then return + if (!Array.isArray(parsed.configurations) || parsed.configurations.length === 0) { + throw Error('Invalid configurations in launch.json'); } if (!parsed.version) { throw Error('Missing field in launch.json: version'); @@ -42,3 +41,18 @@ export async function getConfigurationsByUri(uri?: Uri): Promise e.uri.fsPath).join(';'); + } + if (!workspaceString) { traceError('No workspace folder found'); return Promise.resolve(new Disposable(() => {})); } // create a stable hash for the workspace folder, reduce terminal variable churn const hash = crypto.createHash('sha256'); - hash.update(workspaceUri.toString()); + hash.update(workspaceString.toString()); const stableWorkspaceHash = hash.digest('hex').slice(0, 16); const tempDirPath = path.join(extPath, '.noConfigDebugAdapterEndpoints'); @@ -65,7 +68,7 @@ export async function registerNoConfigDebug( collection.replace('PYDEVD_DISABLE_FILE_VALIDATION', '1'); // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH - collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', tempFilePath); + collection.replace('VSCODE_DEBUGPY_ADAPTER_ENDPOINTS', tempFilePath); const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); const pathSeparator = process.platform === 'win32' ? ';' : ':'; diff --git a/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts new file mode 100644 index 00000000..fc1fd45b --- /dev/null +++ b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts @@ -0,0 +1,116 @@ +'use strict'; + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as typemoq from 'typemoq'; +import * as fs from 'fs-extra'; +import { WorkspaceFolder, Uri, WorkspaceConfiguration } from 'vscode'; +import { + getConfigurationsForWorkspace, + getConfigurationsFromSettings, +} from '../../../../extension/debugger/configuration/launch.json/launchJsonReader'; +import * as vscodeapi from '../../../../extension/common/vscodeapi'; + +suite('Debugging - launchJsonReader', () => { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + suite('getConfigurationsForWorkspace', () => { + test('Should return configurations from launch.json if it exists', async () => { + const workspace = typemoq.Mock.ofType(); + workspace.setup((w) => w.uri).returns(() => Uri.file('/path/to/workspace')); + + const launchJsonContent = `{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Program", + "type": "python", + "request": "launch", + "program": "${workspace.object.uri}/app.py" + } + ] + }`; + + sandbox.stub(fs, 'pathExists').resolves(true); + sandbox.stub(fs, 'readFile').resolves(launchJsonContent); + + const configurations = await getConfigurationsForWorkspace(workspace.object); + assert.strictEqual(configurations.length, 1); + assert.strictEqual(configurations[0].name, 'Launch Program'); + }); + + test('Should return configurations from settings.json if launch.json does not exist', async () => { + const workspace = typemoq.Mock.ofType(); + workspace.setup((w) => w.uri).returns(() => Uri.file('/path/to/workspace')); + + const mockConfig = typemoq.Mock.ofType(); + mockConfig + .setup((c) => c.configurations) + .returns(() => [ + { + name: 'Launch Program 2', + type: 'python', + request: 'launch', + program: '${workspaceFolder}/app.py', + }, + ]); + + sandbox.stub(fs, 'pathExists').resolves(false); + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); + + const configurations = await getConfigurationsForWorkspace(workspace.object); + assert.strictEqual(configurations.length, 1); + assert.strictEqual(configurations[0].name, 'Launch Program 2'); + }); + }); + + suite('getConfigurationsFromSettings', () => { + test('Should return configurations from settings.json', () => { + const workspace = typemoq.Mock.ofType(); + workspace.setup((w) => w.uri).returns(() => Uri.file('/path/to/workspace')); + + const mockConfig = typemoq.Mock.ofType(); + mockConfig + .setup((c) => c.configurations) + .returns(() => [ + { + name: 'Launch Program 3', + type: 'python', + request: 'launch', + program: '${workspaceFolder}/app.py', + }, + ]); + + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); + + const configurations = getConfigurationsFromSettings(workspace.object); + assert.strictEqual(configurations.length, 1); + assert.strictEqual(configurations[0].name, 'Launch Program 3'); + }); + + test('Should error if no configurations in settings.json', () => { + const workspace = typemoq.Mock.ofType(); + workspace.setup((w) => w.uri).returns(() => Uri.file('/path/to/workspace')); + + const mockConfig = typemoq.Mock.ofType(); + mockConfig.setup((c) => c.get('configurations')).returns(() => []); + mockConfig.setup((c) => c.configurations).returns(() => []); + + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); + + assert.throws( + () => getConfigurationsFromSettings(workspace.object), + Error, + 'No configurations found in launch.json or settings.json', + ); + }); + }); +}); diff --git a/src/test/unittest/configuration/providers/fileLaunchWithArgs.unit.test.ts b/src/test/unittest/configuration/providers/fileLaunchWithArgs.unit.test.ts index c829a490..30e0b590 100644 --- a/src/test/unittest/configuration/providers/fileLaunchWithArgs.unit.test.ts +++ b/src/test/unittest/configuration/providers/fileLaunchWithArgs.unit.test.ts @@ -28,7 +28,7 @@ suite('Debugging - Configuration Provider File with Arguments', () => { request: 'launch', program: '${file}', console: 'integratedTerminal', - args: ['${command:pickArgs}'], + args: '${command:pickArgs}', }; expect(state.config).to.be.deep.equal(config);