From f813c4f5b28e7438c6ecb763587c71ad53e73e44 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 5 Feb 2025 09:20:09 -0800 Subject: [PATCH 1/3] get launch config from settings.json as fallback --- .../launch.json/launchJsonReader.ts | 37 ++++-- .../configuration/launch.json/utils.ts | 9 ++ .../launch.json/launchJsonReader.unit.test.ts | 113 ++++++++++++++++++ 3 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 src/extension/debugger/configuration/launch.json/utils.ts create mode 100644 src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts diff --git a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts index e2cad8b2..562feb92 100644 --- a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts +++ b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts @@ -5,26 +5,30 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { parse } from 'jsonc-parser'; import { DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { getConfiguration, getWorkspaceFolder } from '../../../common/vscodeapi'; +import { getWorkspaceFolder } from '../../../common/vscodeapi'; import { traceLog } from '../../../common/log/logging'; +import { getConfiguration } from './utils'; export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise { 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.'); + const settingConfigs = getConfigurationsFromSettings(workspace); + if (settingConfigs.length === 0) { + throw Error('No configurations found in launch.json or settings.json'); + } + return Promise.resolve(settingConfigs); + } + // 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 +46,14 @@ export async function getConfigurationsByUri(uri?: Uri): Promise { + 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(utils, '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(utils, 'getConfiguration').returns(mockConfig.object); + + const configurations = getConfigurationsFromSettings(workspace.object); + assert.strictEqual(configurations.length, 1); + assert.strictEqual(configurations[0].name, 'Launch Program 3'); + }); + + test('Should return empty array 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(utils, 'getConfiguration').returns(mockConfig.object); + + const configurations = getConfigurationsFromSettings(workspace.object); + assert.strictEqual(configurations.length, 0); + }); + }); +}); From 2d208c0b98efd8bb1b8ba32d635b2733d837cda3 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 5 Feb 2025 09:35:54 -0800 Subject: [PATCH 2/3] update error return --- .../configuration/launch.json/launchJsonReader.ts | 14 +++++++------- .../launch.json/launchJsonReader.unit.test.ts | 9 ++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts index 562feb92..e8681bbe 100644 --- a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts +++ b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts @@ -20,11 +20,7 @@ export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): // 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.'); - const settingConfigs = getConfigurationsFromSettings(workspace); - if (settingConfigs.length === 0) { - throw Error('No configurations found in launch.json or settings.json'); - } - return Promise.resolve(settingConfigs); + return getConfigurationsFromSettings(workspace); } // configurations found in launch.json, verify them then return if (!Array.isArray(parsed.configurations) || parsed.configurations.length === 0) { @@ -51,8 +47,12 @@ export function getConfigurationsFromSettings(workspace: WorkspaceFolder): Debug // look in settings.json const codeWorkspaceConfig = getConfiguration('launch', workspace); // if this includes user configs, how do I make sure it selects the workspace ones first - if (!codeWorkspaceConfig.configurations || !Array.isArray(codeWorkspaceConfig.configurations)) { - return []; + if ( + !codeWorkspaceConfig.configurations || + !Array.isArray(codeWorkspaceConfig.configurations) || + codeWorkspaceConfig.configurations.length === 0 + ) { + throw Error('No configurations found in launch.json or settings.json'); } traceLog('Using configuration in workspace settings.json.'); return codeWorkspaceConfig.configurations; diff --git a/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts index d548a473..c076403c 100644 --- a/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts +++ b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts @@ -96,7 +96,7 @@ suite('Debugging - launchJsonReader', () => { assert.strictEqual(configurations[0].name, 'Launch Program 3'); }); - test('Should return empty array if no configurations in settings.json', () => { + 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')); @@ -106,8 +106,11 @@ suite('Debugging - launchJsonReader', () => { sandbox.stub(utils, 'getConfiguration').returns(mockConfig.object); - const configurations = getConfigurationsFromSettings(workspace.object); - assert.strictEqual(configurations.length, 0); + assert.throws( + () => getConfigurationsFromSettings(workspace.object), + Error, + 'No configurations found in launch.json or settings.json', + ); }); }); }); From 25ce53231b2b49d983343d8251254ab3f13eecda Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 10 Feb 2025 13:15:06 -0800 Subject: [PATCH 3/3] remove dup getConfig --- .../configuration/launch.json/launchJsonReader.ts | 3 +-- .../debugger/configuration/launch.json/utils.ts | 9 --------- .../launch.json/launchJsonReader.unit.test.ts | 8 ++++---- 3 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 src/extension/debugger/configuration/launch.json/utils.ts diff --git a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts index e8681bbe..ac906613 100644 --- a/src/extension/debugger/configuration/launch.json/launchJsonReader.ts +++ b/src/extension/debugger/configuration/launch.json/launchJsonReader.ts @@ -5,9 +5,8 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { parse } from 'jsonc-parser'; import { DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { getWorkspaceFolder } from '../../../common/vscodeapi'; +import { getConfiguration, getWorkspaceFolder } from '../../../common/vscodeapi'; import { traceLog } from '../../../common/log/logging'; -import { getConfiguration } from './utils'; export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise { traceLog('Getting configurations for workspace'); diff --git a/src/extension/debugger/configuration/launch.json/utils.ts b/src/extension/debugger/configuration/launch.json/utils.ts deleted file mode 100644 index 925f4c1a..00000000 --- a/src/extension/debugger/configuration/launch.json/utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Resource } from '@vscode/python-extension'; -import { WorkspaceConfiguration, workspace } from 'vscode'; - -export function getConfiguration(section: string, scope?: Resource): WorkspaceConfiguration { - return workspace.getConfiguration(section, scope); -} diff --git a/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts index c076403c..fc1fd45b 100644 --- a/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts +++ b/src/test/unittest/configuration/launch.json/launchJsonReader.unit.test.ts @@ -9,7 +9,7 @@ import { getConfigurationsForWorkspace, getConfigurationsFromSettings, } from '../../../../extension/debugger/configuration/launch.json/launchJsonReader'; -import * as utils from '../../../../extension/debugger/configuration/launch.json/utils'; +import * as vscodeapi from '../../../../extension/common/vscodeapi'; suite('Debugging - launchJsonReader', () => { let sandbox: sinon.SinonSandbox; @@ -64,7 +64,7 @@ suite('Debugging - launchJsonReader', () => { ]); sandbox.stub(fs, 'pathExists').resolves(false); - sandbox.stub(utils, 'getConfiguration').returns(mockConfig.object); + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); const configurations = await getConfigurationsForWorkspace(workspace.object); assert.strictEqual(configurations.length, 1); @@ -89,7 +89,7 @@ suite('Debugging - launchJsonReader', () => { }, ]); - sandbox.stub(utils, 'getConfiguration').returns(mockConfig.object); + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); const configurations = getConfigurationsFromSettings(workspace.object); assert.strictEqual(configurations.length, 1); @@ -104,7 +104,7 @@ suite('Debugging - launchJsonReader', () => { mockConfig.setup((c) => c.get('configurations')).returns(() => []); mockConfig.setup((c) => c.configurations).returns(() => []); - sandbox.stub(utils, 'getConfiguration').returns(mockConfig.object); + sandbox.stub(vscodeapi, 'getConfiguration').returns(mockConfig.object); assert.throws( () => getConfigurationsFromSettings(workspace.object),