diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts index c2e8593797f0..c5413b3662e0 100644 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/debugConfigurationService.ts @@ -8,14 +8,22 @@ import { cloneDeep } from 'lodash'; import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { - IMultiStepInput, IMultiStepInputFactory, InputStep, IQuickPickParameters, + MultiStepInput, } from '../../../common/utils/multiStepInput'; import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; -import { IDebugConfigurationProviderFactory, IDebugConfigurationResolver } from './types'; +import { buildDjangoLaunchDebugConfiguration } from './providers/djangoLaunch'; +import { buildFastAPILaunchDebugConfiguration } from './providers/fastapiLaunch'; +import { buildFileLaunchDebugConfiguration } from './providers/fileLaunch'; +import { buildFlaskLaunchDebugConfiguration } from './providers/flaskLaunch'; +import { buildModuleLaunchConfiguration } from './providers/moduleLaunch'; +import { buildPidAttachConfiguration } from './providers/pidAttach'; +import { buildPyramidLaunchConfiguration } from './providers/pyramidLaunch'; +import { buildRemoteAttachConfiguration } from './providers/remoteAttach'; +import { IDebugConfigurationResolver } from './types'; @injectable() export class PythonDebugConfigurationService implements IDebugConfigurationService { @@ -27,8 +35,6 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationResolver) @named('launch') private readonly launchResolver: IDebugConfigurationResolver, - @inject(IDebugConfigurationProviderFactory) - private readonly providerFactory: IDebugConfigurationProviderFactory, @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, ) {} @@ -102,7 +108,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi } protected async pickDebugConfiguration( - input: IMultiStepInput, + input: MultiStepInput, state: DebugConfigurationState, ): Promise | void> { type DebugConfigurationQuickPickItem = QuickPickItem & { type: DebugConfigurationType }; @@ -148,6 +154,22 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi description: DebugConfigStrings.pyramid.selectConfiguration.description, }, ]; + const debugConfigurations = new Map< + DebugConfigurationType, + ( + input: MultiStepInput, + state: DebugConfigurationState, + ) => Promise> + >(); + debugConfigurations.set(DebugConfigurationType.launchDjango, buildDjangoLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFastAPI, buildFastAPILaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFile, buildFileLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchFlask, buildFlaskLaunchDebugConfiguration); + debugConfigurations.set(DebugConfigurationType.launchModule, buildModuleLaunchConfiguration); + debugConfigurations.set(DebugConfigurationType.pidAttach, buildPidAttachConfiguration); + debugConfigurations.set(DebugConfigurationType.remoteAttach, buildRemoteAttachConfiguration); + debugConfigurations.set(DebugConfigurationType.launchPyramid, buildPyramidLaunchConfiguration); + state.config = {}; const pick = await input.showQuickPick< DebugConfigurationQuickPickItem, @@ -159,8 +181,8 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi items: items, }); if (pick) { - const provider = this.providerFactory.create(pick.type); - return provider.buildConfiguration.bind(provider); + const pickedDebugConfiguration = debugConfigurations.get(pick.type)!; + return pickedDebugConfiguration(input, state); } } } diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts index 0b80f9e0cf14..da4e199fcfc4 100644 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts @@ -3,93 +3,84 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import * as vscode from 'vscode'; import * as path from 'path'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; +import * as fs from 'fs-extra'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; +import { resolveVariables } from '../utils/common'; const workspaceFolderToken = '${workspaceFolder}'; -@injectable() -export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils, - ) {} - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const program = await this.getManagePyPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const defaultProgram = `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; - const config: Partial = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: program || defaultProgram, - args: ['runserver'], - django: true, - justMyCode: true, - }; - if (!program) { - const selectedProgram = await input.showInputBox({ - title: DebugConfigStrings.django.enterManagePyPath.title, - value: defaultProgram, - prompt: DebugConfigStrings.django.enterManagePyPath.prompt, - validate: (value) => this.validateManagePy(state.folder, defaultProgram, value), - }); - if (selectedProgram) { - manuallyEnteredAValue = true; - config.program = selectedProgram; - } +export async function buildDjangoLaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const program = await getManagePyPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const defaultProgram = `${workspaceFolderToken}${path.sep}manage.py`; + const config: Partial = { + name: DebugConfigStrings.django.snippet.name, + type: DebuggerTypeName, + request: 'launch', + program: program || defaultProgram, + args: ['runserver'], + django: true, + justMyCode: true, + }; + if (!program) { + const selectedProgram = await input.showInputBox({ + title: DebugConfigStrings.django.enterManagePyPath.title, + value: defaultProgram, + prompt: DebugConfigStrings.django.enterManagePyPath.prompt, + validate: (value) => validateManagePy(state.folder, defaultProgram, value), + }); + if (selectedProgram) { + manuallyEnteredAValue = true; + config.program = selectedProgram; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - autoDetectedDjangoManagePyPath: !!program, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + autoDetectedDjangoManagePyPath: !!program, + manuallyEnteredAValue, + }); + + Object.assign(state.config, config); +} + +export async function validateManagePy( + folder: vscode.WorkspaceFolder | undefined, + defaultValue: string, + selected?: string, +): Promise { + const error = DebugConfigStrings.django.enterManagePyPath.invalid; + if (!selected || selected.trim().length === 0) { + return error; } - public async validateManagePy( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, - ): Promise { - const error = DebugConfigStrings.django.enterManagePyPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = this.resolveVariables(selected, folder ? folder.uri : undefined); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { - return error; - } - return; + const resolvedPath = resolveVariables(selected, undefined, folder); + + if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { + return error; } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); + if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { + return error; } + return; +} - protected async getManagePyPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; - } +export async function getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { + if (!folder) { + return; + } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}manage.py`; } } diff --git a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts index a534ec21379c..6a4d3676ccab 100644 --- a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts @@ -3,69 +3,64 @@ 'use strict'; -import { inject, injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FastAPILaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IFileSystem) private fs: IFileSystem) {} - public isSupported(debugConfigurationType: DebugConfigurationType): boolean { - return debugConfigurationType === DebugConfigurationType.launchFastAPI; - } - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.fastapi.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'uvicorn', - args: ['main:app'], - jinja: true, - justMyCode: true, - }; +export async function buildFastAPILaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const application = await getApplicationPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.fastapi.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'uvicorn', + args: ['main:app'], + jinja: true, + justMyCode: true, + }; - if (!application) { - const selectedPath = await input.showInputBox({ - title: DebugConfigStrings.fastapi.enterAppPathOrNamePath.title, - value: 'main.py', - prompt: DebugConfigStrings.fastapi.enterAppPathOrNamePath.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 - ? undefined - : DebugConfigStrings.fastapi.enterAppPathOrNamePath.invalid, - ), - }); - if (selectedPath) { - manuallyEnteredAValue = true; - config.args = [`${path.basename(selectedPath, '.py').replace('/', '.')}:app`]; - } + if (!application) { + const selectedPath = await input.showInputBox({ + title: DebugConfigStrings.fastapi.enterAppPathOrNamePath.title, + value: 'main.py', + prompt: DebugConfigStrings.fastapi.enterAppPathOrNamePath.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 + ? undefined + : DebugConfigStrings.fastapi.enterAppPathOrNamePath.invalid, + ), + }); + if (selectedPath) { + manuallyEnteredAValue = true; + config.args = [`${path.basename(selectedPath, '.py').replace('/', '.')}:app`]; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchFastAPI, - autoDetectedFastAPIMainPyPath: !!application, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFastAPI, + autoDetectedFastAPIMainPyPath: !!application, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} +export async function getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return 'main.py'; - } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return 'main.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/fileLaunch.ts b/src/client/debugger/extension/configuration/providers/fileLaunch.ts index d6d51cb0528c..6fcd9d671aad 100644 --- a/src/client/debugger/extension/configuration/providers/fileLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/fileLaunch.ts @@ -3,31 +3,28 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; +import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FileLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.launchFile }, - false, - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.file.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: '${file}', - console: 'integratedTerminal', - justMyCode: true, - }; - Object.assign(state.config, config); - } +export async function buildFileLaunchDebugConfiguration( + _input: MultiStepInput, + state: DebugConfigurationState, +) { + const config: Partial = { + name: DebugConfigStrings.file.snippet.name, + type: DebuggerTypeName, + request: 'launch', + program: '${file}', + console: 'integratedTerminal', + justMyCode: true, + }; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFastAPI, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts index 034308c73cda..4433caa6138a 100644 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts @@ -3,73 +3,68 @@ 'use strict'; -import { inject, injectable } from 'inversify'; import * as path from 'path'; +import * as fs from 'fs-extra'; import { WorkspaceFolder } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class FlaskLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IFileSystem) private fs: IFileSystem) {} - public isSupported(debugConfigurationType: DebugConfigurationType): boolean { - return debugConfigurationType === DebugConfigurationType.launchFlask; - } - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.flask.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: application || 'app.py', - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }; +export async function buildFlaskLaunchDebugConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const application = await getApplicationPath(state.folder); + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.flask.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'flask', + env: { + FLASK_APP: application || 'app.py', + FLASK_DEBUG: '1', + }, + args: ['run', '--no-debugger', '--no-reload'], + jinja: true, + justMyCode: true, + }; - if (!application) { - const selectedApp = await input.showInputBox({ - title: DebugConfigStrings.flask.enterAppPathOrNamePath.title, - value: 'app.py', - prompt: DebugConfigStrings.flask.enterAppPathOrNamePath.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 - ? undefined - : DebugConfigStrings.flask.enterAppPathOrNamePath.invalid, - ), - }); - if (selectedApp) { - manuallyEnteredAValue = true; - config.env!.FLASK_APP = selectedApp; - } + if (!application) { + const selectedApp = await input.showInputBox({ + title: DebugConfigStrings.flask.enterAppPathOrNamePath.title, + value: 'app.py', + prompt: DebugConfigStrings.flask.enterAppPathOrNamePath.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 + ? undefined + : DebugConfigStrings.flask.enterAppPathOrNamePath.invalid, + ), + }); + if (selectedApp) { + manuallyEnteredAValue = true; + config.env!.FLASK_APP = selectedApp; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchFlask, - autoDetectedFlaskAppPyPath: !!application, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchFlask, + autoDetectedFlaskAppPyPath: !!application, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} +export async function getApplicationPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return 'app.py'; - } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return 'app.py'; } } diff --git a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts index 9134655e1143..1b644833f593 100644 --- a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts @@ -3,44 +3,43 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class ModuleLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.module.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: DebugConfigStrings.module.snippet.default, - justMyCode: true, - }; - const selectedModule = await input.showInputBox({ - title: DebugConfigStrings.module.enterModule.title, - value: config.module || DebugConfigStrings.module.enterModule.default, - prompt: DebugConfigStrings.module.enterModule.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.module.enterModule.invalid, - ), - }); - if (selectedModule) { - manuallyEnteredAValue = true; - config.module = selectedModule; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchModule, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); +export async function buildModuleLaunchConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + let manuallyEnteredAValue: boolean | undefined; + const config: Partial = { + name: DebugConfigStrings.module.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: DebugConfigStrings.module.snippet.default, + justMyCode: true, + }; + const selectedModule = await input.showInputBox({ + title: DebugConfigStrings.module.enterModule.title, + value: config.module || DebugConfigStrings.module.enterModule.default, + prompt: DebugConfigStrings.module.enterModule.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 ? undefined : DebugConfigStrings.module.enterModule.invalid, + ), + }); + if (selectedModule) { + manuallyEnteredAValue = true; + config.module = selectedModule; } + + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchModule, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/pidAttach.ts b/src/client/debugger/extension/configuration/providers/pidAttach.ts index 8225ec175e65..c9bb7656d6ca 100644 --- a/src/client/debugger/extension/configuration/providers/pidAttach.ts +++ b/src/client/debugger/extension/configuration/providers/pidAttach.ts @@ -3,30 +3,27 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; +import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -@injectable() -export class PidAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.pidAttach }, - false, - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.attachPid.snippet.name, - type: DebuggerTypeName, - request: 'attach', - processId: '${command:pickProcess}', - justMyCode: true, - }; - Object.assign(state.config, config); - } +export async function buildPidAttachConfiguration( + _input: MultiStepInput, + state: DebugConfigurationState, +) { + const config: Partial = { + name: DebugConfigStrings.attachPid.snippet.name, + type: DebuggerTypeName, + request: 'attach', + processId: '${command:pickProcess}', + justMyCode: true, + }; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.pidAttach, + }); + Object.assign(state.config, config); } diff --git a/src/client/debugger/extension/configuration/providers/providerFactory.ts b/src/client/debugger/extension/configuration/providers/providerFactory.ts deleted file mode 100644 index 18f5a18a9ef4..000000000000 --- a/src/client/debugger/extension/configuration/providers/providerFactory.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; -import { IDebugConfigurationProviderFactory } from '../types'; - -@injectable() -export class DebugConfigurationProviderFactory implements IDebugConfigurationProviderFactory { - private readonly providers: Map; - constructor( - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFastAPI) - fastapiProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFlask) - flaskProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchDjango) - djangoProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchModule) - moduleProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFile) - fileProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchPyramid) - pyramidProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.remoteAttach) - remoteAttachProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.pidAttach) - pidAttachProvider: IDebugConfigurationProvider, - ) { - this.providers = new Map(); - this.providers.set(DebugConfigurationType.launchDjango, djangoProvider); - this.providers.set(DebugConfigurationType.launchFastAPI, fastapiProvider); - this.providers.set(DebugConfigurationType.launchFlask, flaskProvider); - this.providers.set(DebugConfigurationType.launchFile, fileProvider); - this.providers.set(DebugConfigurationType.launchModule, moduleProvider); - this.providers.set(DebugConfigurationType.launchPyramid, pyramidProvider); - this.providers.set(DebugConfigurationType.remoteAttach, remoteAttachProvider); - this.providers.set(DebugConfigurationType.pidAttach, pidAttachProvider); - } - public create(configurationType: DebugConfigurationType): IDebugConfigurationProvider { - return this.providers.get(configurationType)!; - } -} diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts index 04233df11039..dd82518720c4 100644 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts @@ -3,105 +3,94 @@ 'use strict'; -import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; +import * as fs from 'fs-extra'; +import { WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; import * as nls from 'vscode-nls'; +import { resolveVariables } from '../utils/common'; const localize: nls.LocalizeFunc = nls.loadMessageBundle(); const workspaceFolderToken = '${workspaceFolder}'; -@injectable() -export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils, - ) {} - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const iniPath = await this.getDevelopmentIniPath(state.folder); - const defaultIni = `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; - let manuallyEnteredAValue: boolean | undefined; +export async function buildPyramidLaunchConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +) { + const iniPath = await getDevelopmentIniPath(state.folder); + const defaultIni = `${workspaceFolderToken}${path.sep}development.ini`; + let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.pyramid.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: [iniPath || defaultIni], - pyramid: true, - jinja: true, - justMyCode: true, - }; + const config: Partial = { + name: DebugConfigStrings.pyramid.snippet.name, + type: DebuggerTypeName, + request: 'launch', + module: 'pyramid.scripts.pserve', + args: [iniPath || defaultIni], + pyramid: true, + jinja: true, + justMyCode: true, + }; - if (!iniPath) { - const selectedIniPath = await input.showInputBox({ - title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title, - value: defaultIni, - prompt: localize( - 'debug.pyramidEnterDevelopmentIniPathPrompt', - 'Enter the path to development.ini ({0} points to the root of the current workspace folder)', - workspaceFolderToken, - ), - validate: (value) => this.validateIniPath(state ? state.folder : undefined, defaultIni, value), - }); - if (selectedIniPath) { - manuallyEnteredAValue = true; - config.args = [selectedIniPath]; - } + if (!iniPath) { + const selectedIniPath = await input.showInputBox({ + title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title, + value: defaultIni, + prompt: localize( + 'debug.pyramidEnterDevelopmentIniPathPrompt', + 'Enter the path to development.ini ({0} points to the root of the current workspace folder)', + workspaceFolderToken, + ), + validate: (value) => validateIniPath(state ? state.folder : undefined, defaultIni, value), + }); + if (selectedIniPath) { + manuallyEnteredAValue = true; + config.args = [selectedIniPath]; } + } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchPyramid, - autoDetectedPyramidIniPath: !!iniPath, - manuallyEnteredAValue, - }); - Object.assign(state.config, config); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchPyramid, + autoDetectedPyramidIniPath: !!iniPath, + manuallyEnteredAValue, + }); + Object.assign(state.config, config); +} + +export async function validateIniPath( + folder: WorkspaceFolder | undefined, + defaultValue: string, + selected?: string, +): Promise { + if (!folder) { + return; } - public async validateIniPath( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, - ): Promise { - if (!folder) { - return; - } - const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = this.resolveVariables(selected, folder.uri); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { - return error; - } + const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid; + if (!selected || selected.trim().length === 0) { + return error; } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); + const resolvedPath = resolveVariables(selected, undefined, folder); + if (selected !== defaultValue && !fs.pathExists(resolvedPath)) { + return error; } + if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { + return error; + } +} - protected async getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; - } +export async function getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { + if (!folder) { + return; + } + const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); + if (await fs.pathExists(defaultLocationOfManagePy)) { + return `${workspaceFolderToken}${path.sep}development.ini`; } } diff --git a/src/client/debugger/extension/configuration/providers/remoteAttach.ts b/src/client/debugger/extension/configuration/providers/remoteAttach.ts index c97ade2a83ef..a43c48b664af 100644 --- a/src/client/debugger/extension/configuration/providers/remoteAttach.ts +++ b/src/client/debugger/extension/configuration/providers/remoteAttach.ts @@ -3,90 +3,59 @@ 'use strict'; -import { injectable } from 'inversify'; import { DebugConfigStrings } from '../../../../common/utils/localize'; import { InputStep, MultiStepInput } from '../../../../common/utils/multiStepInput'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; +import { configurePort } from '../utils/configuration'; const defaultHost = 'localhost'; const defaultPort = 5678; -@injectable() -export class RemoteAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ): Promise | void> { - const config: Partial = { - name: DebugConfigStrings.attach.snippet.name, - type: DebuggerTypeName, - request: 'attach', - connect: { - host: defaultHost, - port: defaultPort, +export async function buildRemoteAttachConfiguration( + input: MultiStepInput, + state: DebugConfigurationState, +): Promise | void> { + const config: Partial = { + name: DebugConfigStrings.attach.snippet.name, + type: DebuggerTypeName, + request: 'attach', + connect: { + host: defaultHost, + port: defaultPort, + }, + pathMappings: [ + { + localRoot: '${workspaceFolder}', + remoteRoot: '.', }, - pathMappings: [ - { - localRoot: '${workspaceFolder}', - remoteRoot: '.', - }, - ], - justMyCode: true, - }; + ], + justMyCode: true, + }; - const connect = config.connect!; - connect.host = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemoteHost.title, - step: 1, - totalSteps: 2, - value: connect.host || defaultHost, - prompt: DebugConfigStrings.attach.enterRemoteHost.prompt, - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.attach.enterRemoteHost.invalid, - ), - }); - if (!connect.host) { - connect.host = defaultHost; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.host !== defaultHost, - }); - Object.assign(state.config, config); - return (_) => this.configurePort(input, state.config); + const connect = config.connect!; + connect.host = await input.showInputBox({ + title: DebugConfigStrings.attach.enterRemoteHost.title, + step: 1, + totalSteps: 2, + value: connect.host || defaultHost, + prompt: DebugConfigStrings.attach.enterRemoteHost.prompt, + validate: (value) => + Promise.resolve( + value && value.trim().length > 0 ? undefined : DebugConfigStrings.attach.enterRemoteHost.invalid, + ), + }); + if (!connect.host) { + connect.host = defaultHost; } - protected async configurePort( - input: MultiStepInput, - config: Partial, - ) { - const connect = config.connect || (config.connect = {}); - const port = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemotePort.title, - step: 2, - totalSteps: 2, - value: (connect.port || defaultPort).toString(), - prompt: DebugConfigStrings.attach.enterRemotePort.prompt, - validate: (value) => - Promise.resolve( - value && /^\d+$/.test(value.trim()) ? undefined : DebugConfigStrings.attach.enterRemotePort.invalid, - ), - }); - if (port && /^\d+$/.test(port.trim())) { - connect.port = parseInt(port, 10); - } - if (!connect.port) { - connect.port = defaultPort; - } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.port !== defaultPort, - }); - } + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.remoteAttach, + manuallyEnteredAValue: connect.host !== defaultHost, + }); + Object.assign(state.config, config); + return (_) => configurePort(input, state.config); } diff --git a/src/client/debugger/extension/configuration/types.ts b/src/client/debugger/extension/configuration/types.ts index 0dbf32c8d68b..c888fc89ddec 100644 --- a/src/client/debugger/extension/configuration/types.ts +++ b/src/client/debugger/extension/configuration/types.ts @@ -4,7 +4,6 @@ 'use strict'; import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../types'; export const IDebugConfigurationResolver = Symbol('IDebugConfigurationResolver'); export interface IDebugConfigurationResolver { @@ -21,11 +20,6 @@ export interface IDebugConfigurationResolver { ): Promise; } -export const IDebugConfigurationProviderFactory = Symbol('IDebugConfigurationProviderFactory'); -export interface IDebugConfigurationProviderFactory { - create(configurationType: DebugConfigurationType): IDebugConfigurationProvider; -} - export const ILaunchJsonReader = Symbol('ILaunchJsonReader'); export interface ILaunchJsonReader { getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise; diff --git a/src/client/debugger/extension/configuration/utils/common.ts b/src/client/debugger/extension/configuration/utils/common.ts new file mode 100644 index 000000000000..4767d7c928e0 --- /dev/null +++ b/src/client/debugger/extension/configuration/utils/common.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { WorkspaceFolder } from 'vscode'; +import { getWorkspaceFolder } from './workspaceFolder'; + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +function isString(str: any): str is string { + if (typeof str === 'string' || str instanceof String) { + return true; + } + + return false; +} + +export function resolveVariables( + value: string, + rootFolder: string | undefined, + folder: WorkspaceFolder | undefined, +): string { + const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined; + const variablesObject: { [key: string]: any } = {}; + variablesObject.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder; + + const regexp = /\$\{(.*?)\}/g; + return value.replace(regexp, (match: string, name: string) => { + const newValue = variablesObject[name]; + if (isString(newValue)) { + return newValue; + } + return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; + }); +} diff --git a/src/client/debugger/extension/configuration/utils/configuration.ts b/src/client/debugger/extension/configuration/utils/configuration.ts new file mode 100644 index 000000000000..37fb500dbfdd --- /dev/null +++ b/src/client/debugger/extension/configuration/utils/configuration.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { DebugConfigStrings } from '../../../../common/utils/localize'; +import { MultiStepInput } from '../../../../common/utils/multiStepInput'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; +import { AttachRequestArguments } from '../../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../types'; + +const defaultPort = 5678; + +export async function configurePort( + input: MultiStepInput, + config: Partial, +): Promise { + const connect = config.connect || (config.connect = {}); + const port = await input.showInputBox({ + title: DebugConfigStrings.attach.enterRemotePort.title, + step: 2, + totalSteps: 2, + value: (connect.port || defaultPort).toString(), + prompt: DebugConfigStrings.attach.enterRemotePort.prompt, + validate: (value) => + Promise.resolve( + value && /^\d+$/.test(value.trim()) ? undefined : DebugConfigStrings.attach.enterRemotePort.invalid, + ), + }); + if (port && /^\d+$/.test(port.trim())) { + connect.port = parseInt(port, 10); + } + if (!connect.port) { + connect.port = defaultPort; + } + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.remoteAttach, + manuallyEnteredAValue: connect.port !== defaultPort, + }); +} diff --git a/src/client/debugger/extension/configuration/utils/workspaceFolder.ts b/src/client/debugger/extension/configuration/utils/workspaceFolder.ts new file mode 100644 index 000000000000..ddd98c751562 --- /dev/null +++ b/src/client/debugger/extension/configuration/utils/workspaceFolder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; + +export function getWorkspaceFolder(uri: vscode.Uri): vscode.WorkspaceFolder | undefined { + return vscode.workspace.getWorkspaceFolder(uri); +} diff --git a/src/client/debugger/extension/serviceRegistry.ts b/src/client/debugger/extension/serviceRegistry.ts index 3ffdf5193d2b..4322e9d31df5 100644 --- a/src/client/debugger/extension/serviceRegistry.ts +++ b/src/client/debugger/extension/serviceRegistry.ts @@ -19,31 +19,16 @@ import { LaunchJsonCompletionProvider } from './configuration/launch.json/comple import { InterpreterPathCommand } from './configuration/launch.json/interpreterPathCommand'; import { LaunchJsonReader } from './configuration/launch.json/launchJsonReader'; import { LaunchJsonUpdaterService } from './configuration/launch.json/updaterService'; -import { DjangoLaunchDebugConfigurationProvider } from './configuration/providers/djangoLaunch'; -import { FastAPILaunchDebugConfigurationProvider } from './configuration/providers/fastapiLaunch'; -import { FileLaunchDebugConfigurationProvider } from './configuration/providers/fileLaunch'; -import { FlaskLaunchDebugConfigurationProvider } from './configuration/providers/flaskLaunch'; -import { ModuleLaunchDebugConfigurationProvider } from './configuration/providers/moduleLaunch'; -import { PidAttachDebugConfigurationProvider } from './configuration/providers/pidAttach'; -import { DebugConfigurationProviderFactory } from './configuration/providers/providerFactory'; -import { PyramidLaunchDebugConfigurationProvider } from './configuration/providers/pyramidLaunch'; -import { RemoteAttachDebugConfigurationProvider } from './configuration/providers/remoteAttach'; import { AttachConfigurationResolver } from './configuration/resolvers/attach'; import { DebugEnvironmentVariablesHelper, IDebugEnvironmentVariablesService } from './configuration/resolvers/helper'; import { LaunchConfigurationResolver } from './configuration/resolvers/launch'; -import { - IDebugConfigurationProviderFactory, - IDebugConfigurationResolver, - ILaunchJsonReader, -} from './configuration/types'; +import { IDebugConfigurationResolver, ILaunchJsonReader } from './configuration/types'; import { DebugCommands } from './debugCommands'; import { ChildProcessAttachEventHandler } from './hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from './hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './hooks/types'; import { - DebugConfigurationType, IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, IDebugConfigurationService, IDebuggerBanner, IDebugSessionLoggingFactory, @@ -85,50 +70,6 @@ export function registerTypes(serviceManager: IServiceManager) { AttachConfigurationResolver, 'attach', ); - serviceManager.addSingleton( - IDebugConfigurationProviderFactory, - DebugConfigurationProviderFactory, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FileLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFile, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - DjangoLaunchDebugConfigurationProvider, - DebugConfigurationType.launchDjango, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FastAPILaunchDebugConfigurationProvider, - DebugConfigurationType.launchFastAPI, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FlaskLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFlask, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - RemoteAttachDebugConfigurationProvider, - DebugConfigurationType.remoteAttach, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - ModuleLaunchDebugConfigurationProvider, - DebugConfigurationType.launchModule, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PyramidLaunchDebugConfigurationProvider, - DebugConfigurationType.launchPyramid, - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PidAttachDebugConfigurationProvider, - DebugConfigurationType.pidAttach, - ); serviceManager.addSingleton( IDebugEnvironmentVariablesService, DebugEnvironmentVariablesHelper, diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index 273a398f6e40..1e5b724975a9 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -13,7 +13,6 @@ import { WorkspaceFolder, } from 'vscode'; -import { InputStep, MultiStepInput } from '../../common/utils/multiStepInput'; import { DebugConfigurationArguments } from '../types'; export const IDebugConfigurationService = Symbol('IDebugConfigurationService'); @@ -27,18 +26,11 @@ export interface IDebuggerBanner { initialize(): void; } -export const IDebugConfigurationProvider = Symbol('IDebugConfigurationProvider'); export type DebugConfigurationState = { config: Partial; folder?: WorkspaceFolder; token?: CancellationToken; }; -export interface IDebugConfigurationProvider { - buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ): Promise | void>; -} export enum DebugConfigurationType { launchFile = 'launchFile', diff --git a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts b/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts index 8495d4820c0a..85c45407a137 100644 --- a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts +++ b/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts @@ -4,12 +4,10 @@ 'use strict'; import { expect } from 'chai'; -import { instance, mock } from 'ts-mockito'; import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; -import { IMultiStepInput, IMultiStepInputFactory } from '../../../../client/common/utils/multiStepInput'; +import { IMultiStepInputFactory, MultiStepInput } from '../../../../client/common/utils/multiStepInput'; import { PythonDebugConfigurationService } from '../../../../client/debugger/extension/configuration/debugConfigurationService'; -import { DebugConfigurationProviderFactory } from '../../../../client/debugger/extension/configuration/providers/providerFactory'; import { IDebugConfigurationResolver } from '../../../../client/debugger/extension/configuration/types'; import { DebugConfigurationState } from '../../../../client/debugger/extension/types'; import { AttachRequestArguments, LaunchRequestArguments } from '../../../../client/debugger/types'; @@ -19,11 +17,10 @@ suite('Debugging - Configuration Service', () => { let launchResolver: typemoq.IMock>; let configService: TestPythonDebugConfigurationService; let multiStepFactory: typemoq.IMock; - let providerFactory: DebugConfigurationProviderFactory; class TestPythonDebugConfigurationService extends PythonDebugConfigurationService { public async pickDebugConfiguration( - input: IMultiStepInput, + input: MultiStepInput, state: DebugConfigurationState, ) { return super.pickDebugConfiguration(input, state); @@ -33,12 +30,10 @@ suite('Debugging - Configuration Service', () => { attachResolver = typemoq.Mock.ofType>(); launchResolver = typemoq.Mock.ofType>(); multiStepFactory = typemoq.Mock.ofType(); - providerFactory = mock(DebugConfigurationProviderFactory); configService = new TestPythonDebugConfigurationService( attachResolver.object, launchResolver.object, - instance(providerFactory), multiStepFactory.object, ); }); @@ -93,7 +88,7 @@ suite('Debugging - Configuration Service', () => { }); test('Picker should be displayed', async () => { const state = ({ configs: [], folder: {}, token: undefined } as any) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); + const multiStepInput = typemoq.Mock.ofType>(); multiStepInput .setup((i) => i.showQuickPick(typemoq.It.isAny())) .returns(() => Promise.resolve(undefined as any)) @@ -105,7 +100,7 @@ suite('Debugging - Configuration Service', () => { }); test('Existing Configuration items must be removed before displaying picker', async () => { const state = ({ configs: [1, 2, 3], folder: {}, token: undefined } as any) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); + const multiStepInput = typemoq.Mock.ofType>(); multiStepInput .setup((i) => i.showQuickPick(typemoq.It.isAny())) .returns(() => Promise.resolve(undefined as any)) diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts index 6423e19ad76b..479fdfebb53e 100644 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts @@ -3,153 +3,104 @@ 'use strict'; +import { Uri } from 'vscode'; import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../../client/common/application/types'; -import { WorkspaceService } from '../../../../../client/common/application/workspace'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { PathUtils } from '../../../../../client/common/platform/pathUtils'; -import { IFileSystem } from '../../../../../client/common/platform/types'; -import { IPathUtils } from '../../../../../client/common/types'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { DjangoLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; +import * as workspaceFolder from '../../../../../client/debugger/extension/configuration/utils/workspaceFolder'; +import * as djangoLaunch from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; suite('Debugging - Configuration Provider Django', () => { - let fs: IFileSystem; - let workspaceService: IWorkspaceService; - let pathUtils: IPathUtils; - let provider: TestDjangoLaunchDebugConfigurationProvider; + let pathExistsStub: sinon.SinonStub; + let pathSeparatorStub: sinon.SinonStub; + let workspaceStub: sinon.SinonStub; let input: MultiStepInput; - class TestDjangoLaunchDebugConfigurationProvider extends DjangoLaunchDebugConfigurationProvider { - public resolveVariables(pythonPath: string, resource: Uri | undefined): string { - return super.resolveVariables(pythonPath, resource); - } - - public async getManagePyPath(folder: WorkspaceFolder): Promise { - return super.getManagePyPath(folder); - } - } + setup(() => { - fs = mock(FileSystem); - workspaceService = mock(WorkspaceService); - pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestDjangoLaunchDebugConfigurationProvider( - instance(fs), - instance(workspaceService), - instance(pathUtils), - ); + pathExistsStub = sinon.stub(fs, 'pathExists'); + pathSeparatorStub = sinon.stub(path, 'sep'); + workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); + }); + teardown(() => { + sinon.restore(); }); test("getManagePyPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - when(fs.fileExists(managePyPath)).thenResolve(false); - - const file = await provider.getManagePyPath(folder); + pathExistsStub.withArgs(managePyPath).resolves(false); + const file = await djangoLaunch.getManagePyPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); test('getManagePyPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - - when(pathUtils.separator).thenReturn('-'); - when(fs.fileExists(managePyPath)).thenResolve(true); - - const file = await provider.getManagePyPath(folder); + pathExistsStub.withArgs(managePyPath).resolves(true); + pathSeparatorStub.value('-'); + const file = await djangoLaunch.getManagePyPath(folder); expect(file).to.be.equal('${workspaceFolder}-manage.py'); }); test('Resolve variables (with resource)', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(folder); - - const resolvedPath = provider.resolveVariables('${workspaceFolder}/one.py', Uri.file('')); + workspaceStub.returns(folder); + const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder); expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`); }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - - const error = await provider.validateManagePy(folder, ''); + const error = await djangoLaunch.validateManagePy(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - - const error = await provider.validateManagePy(folder, '', ''); + const error = await djangoLaunch.validateManagePy(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => ''; - - const error = await provider.validateManagePy(folder, '', 'x'); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz'; - - when(fs.fileExists('xyz')).thenResolve(false); - const error = await provider.validateManagePy(folder, '', 'x'); + pathExistsStub.withArgs('xyz').resolves(false); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.txt'; - - when(fs.fileExists('xyz.txt')).thenResolve(true); - const error = await provider.validateManagePy(folder, '', 'x'); + pathExistsStub.withArgs('xyz.txt').resolves(true); + const error = await djangoLaunch.validateManagePy(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is python', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.py'; - - when(fs.fileExists('xyz.py')).thenResolve(true); - const error = await provider.validateManagePy(folder, '', 'x'); + pathExistsStub.withArgs('xyz.py').resolves(true); + const error = await djangoLaunch.validateManagePy(folder, '', 'xyz.py'); expect(error).to.be.equal(undefined, 'should not have errors'); }); - test('Launch JSON with valid python path', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve('xyz.py'); - when(pathUtils.separator).thenReturn('-'); - - await provider.buildConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: 'xyz.py', - args: ['runserver'], - django: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); test('Launch JSON with selected managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve(undefined); - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); - - await provider.buildConfiguration(instance(input), state); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.django.snippet.name, @@ -166,14 +117,11 @@ suite('Debugging - Configuration Provider Django', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getManagePyPath = () => Promise.resolve(undefined); const workspaceFolderToken = '${workspaceFolder}'; const defaultProgram = `${workspaceFolderToken}-manage.py`; - - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); - - await provider.buildConfiguration(instance(input), state); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.django.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts index 35ef98ce9cf8..f6c20985e4da 100644 --- a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts @@ -5,55 +5,48 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { IFileSystem } from '../../../../../client/common/platform/types'; +import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FastAPILaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/fastapiLaunch'; +import * as fastApiLaunch from '../../../../../client/debugger/extension/configuration/providers/fastapiLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider FastAPI', () => { - let fs: IFileSystem; - let provider: TestFastAPILaunchDebugConfigurationProvider; let input: MultiStepInput; - class TestFastAPILaunchDebugConfigurationProvider extends FastAPILaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { - return super.getApplicationPath(folder); - } - } + let pathExistsStub: sinon.SinonStub; + setup(() => { - fs = mock(FileSystem); input = mock>(MultiStepInput); - provider = new TestFastAPILaunchDebugConfigurationProvider(instance(fs)); + pathExistsStub = sinon.stub(fs, 'pathExists'); + }); + teardown(() => { + sinon.restore(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); - when(fs.fileExists(appPyPath)).thenResolve(false); - - const file = await provider.getApplicationPath(folder); + pathExistsStub.withArgs(appPyPath).resolves(false); + const file = await fastApiLaunch.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); test('getApplicationPath should find path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'main.py'); - - when(fs.fileExists(appPyPath)).thenResolve(true); - - const file = await provider.getApplicationPath(folder); + pathExistsStub.withArgs(appPyPath).resolves(true); + const file = await fastApiLaunch.getApplicationPath(folder); expect(file).to.be.equal('main.py'); }); test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); - await provider.buildConfiguration(instance(input), state); + await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.fastapi.snippet.name, @@ -70,11 +63,10 @@ suite('Debugging - Configuration Provider FastAPI', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('main'); - await provider.buildConfiguration(instance(input), state); + await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.fastapi.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts index c074ad33a01c..60f2b199bbd2 100644 --- a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts @@ -8,18 +8,14 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FileLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/fileLaunch'; +import { buildFileLaunchDebugConfiguration } from '../../../../../client/debugger/extension/configuration/providers/fileLaunch'; suite('Debugging - Configuration Provider File', () => { - let provider: FileLaunchDebugConfigurationProvider; - setup(() => { - provider = new FileLaunchDebugConfigurationProvider(); - }); test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - await provider.buildConfiguration(undefined as any, state); + await buildFileLaunchDebugConfiguration(undefined as any, state); const config = { name: DebugConfigStrings.file.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts index c8beab640ab1..08fb5259b282 100644 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts @@ -5,55 +5,47 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { IFileSystem } from '../../../../../client/common/platform/types'; +import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { FlaskLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/flaskLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; +import * as flaskLaunch from '../../../../../client/debugger/extension/configuration/providers/flaskLaunch'; suite('Debugging - Configuration Provider Flask', () => { - let fs: IFileSystem; - let provider: TestFlaskLaunchDebugConfigurationProvider; + let pathExistsStub: sinon.SinonStub; let input: MultiStepInput; - class TestFlaskLaunchDebugConfigurationProvider extends FlaskLaunchDebugConfigurationProvider { - public async getApplicationPath(folder: WorkspaceFolder): Promise { - return super.getApplicationPath(folder); - } - } setup(() => { - fs = mock(FileSystem); input = mock>(MultiStepInput); - provider = new TestFlaskLaunchDebugConfigurationProvider(instance(fs)); + pathExistsStub = sinon.stub(fs, 'pathExists'); + }); + teardown(() => { + sinon.restore(); }); test("getApplicationPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); - when(fs.fileExists(appPyPath)).thenResolve(false); - - const file = await provider.getApplicationPath(folder); + pathExistsStub.withArgs(appPyPath).resolves(false); + const file = await flaskLaunch.getApplicationPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); test('getApplicationPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const appPyPath = path.join(folder.uri.fsPath, 'app.py'); - - when(fs.fileExists(appPyPath)).thenResolve(true); - - const file = await provider.getApplicationPath(folder); + pathExistsStub.withArgs(appPyPath).resolves(true); + const file = await flaskLaunch.getApplicationPath(folder); expect(file).to.be.equal('app.py'); }); test('Launch JSON with valid python path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve('xyz.py'); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, @@ -61,7 +53,7 @@ suite('Debugging - Configuration Provider Flask', () => { request: 'launch', module: 'flask', env: { - FLASK_APP: 'xyz.py', + FLASK_APP: 'app.py', FLASK_DEBUG: '1', }, args: ['run', '--no-debugger', '--no-reload'], @@ -74,11 +66,10 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with selected app path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, @@ -99,11 +90,9 @@ suite('Debugging - Configuration Provider Flask', () => { test('Launch JSON with default managepy path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getApplicationPath = () => Promise.resolve(undefined); - when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); const config = { name: DebugConfigStrings.flask.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts index c0571a1bf30f..2508db506ca2 100644 --- a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts @@ -10,14 +10,10 @@ import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { ModuleLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/moduleLaunch'; +import { buildModuleLaunchConfiguration } from '../../../../../client/debugger/extension/configuration/providers/moduleLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Module', () => { - let provider: ModuleLaunchDebugConfigurationProvider; - setup(() => { - provider = new ModuleLaunchDebugConfigurationProvider(); - }); test('Launch JSON with default module name', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; @@ -25,7 +21,7 @@ suite('Debugging - Configuration Provider Module', () => { when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await buildModuleLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.module.snippet.name, @@ -44,7 +40,7 @@ suite('Debugging - Configuration Provider Module', () => { when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await buildModuleLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.module.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts index 696179ea9a12..db9de7e0f58c 100644 --- a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts @@ -8,18 +8,14 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { PidAttachDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/pidAttach'; +import { buildPidAttachConfiguration } from '../../../../../client/debugger/extension/configuration/providers/pidAttach'; suite('Debugging - Configuration Provider File', () => { - let provider: PidAttachDebugConfigurationProvider; - setup(() => { - provider = new PidAttachDebugConfigurationProvider(); - }); test('Launch JSON with default process id', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - await provider.buildConfiguration(undefined as any, state); + await buildPidAttachConfiguration(undefined as any, state); const config = { name: DebugConfigStrings.attachPid.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts b/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts deleted file mode 100644 index a786347ed8d1..000000000000 --- a/src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { getNamesAndValues } from '../../../../../client/common/utils/enum'; -import { DebugConfigurationProviderFactory } from '../../../../../client/debugger/extension/configuration/providers/providerFactory'; -import { IDebugConfigurationProviderFactory } from '../../../../../client/debugger/extension/configuration/types'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider Factory', () => { - let mappedProviders: Map; - let factory: IDebugConfigurationProviderFactory; - setup(() => { - mappedProviders = new Map(); - getNamesAndValues(DebugConfigurationType).forEach((item) => { - mappedProviders.set(item.value, (item.value as any) as IDebugConfigurationProvider); - }); - factory = new DebugConfigurationProviderFactory( - mappedProviders.get(DebugConfigurationType.launchFastAPI)!, - mappedProviders.get(DebugConfigurationType.launchFlask)!, - mappedProviders.get(DebugConfigurationType.launchDjango)!, - mappedProviders.get(DebugConfigurationType.launchModule)!, - mappedProviders.get(DebugConfigurationType.launchFile)!, - mappedProviders.get(DebugConfigurationType.launchPyramid)!, - mappedProviders.get(DebugConfigurationType.remoteAttach)!, - mappedProviders.get(DebugConfigurationType.pidAttach)!, - ); - }); - getNamesAndValues(DebugConfigurationType).forEach((item) => { - test(`Configuration Provider for ${item.name}`, () => { - const provider = factory.create(item.value); - expect(provider).to.equal(mappedProviders.get(item.value)); - }); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts index 04a74dfd819b..8a5d29206180 100644 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts @@ -5,137 +5,109 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as fs from 'fs-extra'; +import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../../client/common/application/types'; -import { WorkspaceService } from '../../../../../client/common/application/workspace'; -import { FileSystem } from '../../../../../client/common/platform/fileSystem'; -import { PathUtils } from '../../../../../client/common/platform/pathUtils'; -import { IFileSystem } from '../../../../../client/common/platform/types'; -import { IPathUtils } from '../../../../../client/common/types'; +import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { PyramidLaunchDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; +import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; +import * as workspaceFolder from '../../../../../client/debugger/extension/configuration/utils/workspaceFolder'; +import * as pyramidLaunch from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; suite('Debugging - Configuration Provider Pyramid', () => { - let fs: IFileSystem; - let workspaceService: IWorkspaceService; - let pathUtils: IPathUtils; - let provider: TestPyramidLaunchDebugConfigurationProvider; let input: MultiStepInput; - class TestPyramidLaunchDebugConfigurationProvider extends PyramidLaunchDebugConfigurationProvider { - public resolveVariables(pythonPath: string, resource: Uri | undefined): string { - return super.resolveVariables(pythonPath, resource); - } - - public async getDevelopmentIniPath(folder: WorkspaceFolder): Promise { - return super.getDevelopmentIniPath(folder); - } - } + let pathExistsStub: sinon.SinonStub; + let pathSeparatorStub: sinon.SinonStub; + let workspaceStub: sinon.SinonStub; + setup(() => { - fs = mock(FileSystem); - workspaceService = mock(WorkspaceService); - pathUtils = mock(PathUtils); input = mock>(MultiStepInput); - provider = new TestPyramidLaunchDebugConfigurationProvider( - instance(fs), - instance(workspaceService), - instance(pathUtils), - ); + pathExistsStub = sinon.stub(fs, 'pathExists'); + pathSeparatorStub = sinon.stub(path, 'sep'); + workspaceStub = sinon.stub(workspaceFolder, 'getWorkspaceFolder'); + }); + teardown(() => { + sinon.restore(); }); test("getDevelopmentIniPath should return undefined if file doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); - when(fs.fileExists(managePyPath)).thenResolve(false); - - const file = await provider.getDevelopmentIniPath(folder); + pathExistsStub.withArgs(managePyPath).resolves(false); + const file = await pyramidLaunch.getDevelopmentIniPath(folder); expect(file).to.be.equal(undefined, 'Should return undefined'); }); test('getDevelopmentIniPath should file path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const managePyPath = path.join(folder.uri.fsPath, 'development.ini'); - - when(pathUtils.separator).thenReturn('-'); - when(fs.fileExists(managePyPath)).thenResolve(true); - - const file = await provider.getDevelopmentIniPath(folder); + pathSeparatorStub.value('-'); + pathExistsStub.withArgs(managePyPath).resolves(true); + const file = await pyramidLaunch.getDevelopmentIniPath(folder); expect(file).to.be.equal('${workspaceFolder}-development.ini'); }); test('Resolve variables (with resource)', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - when(workspaceService.getWorkspaceFolder(anything())).thenReturn(folder); - - const resolvedPath = provider.resolveVariables('${workspaceFolder}/one.py', Uri.file('')); + workspaceStub.returns(folder); + const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder); expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`); }); test('Validation of path should return errors if path is undefined', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - - const error = await provider.validateIniPath(folder, ''); + const error = await pyramidLaunch.validateIniPath(folder, ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - - const error = await provider.validateIniPath(folder, '', ''); + const error = await pyramidLaunch.validateIniPath(folder, '', ''); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is empty', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => ''; - - const error = await provider.validateIniPath(folder, '', 'x'); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test("Validation of path should return errors if resolved path doesn't exist", async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz'; - - when(fs.fileExists('xyz')).thenResolve(false); - const error = await provider.validateIniPath(folder, '', 'x'); + pathExistsStub.withArgs('xyz').resolves(false); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); test('Validation of path should return errors if resolved path is non-ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.txt'; - - when(fs.fileExists('xyz.txt')).thenResolve(true); - const error = await provider.validateIniPath(folder, '', 'x'); + pathExistsStub.withArgs('xyz.txt').resolves(true); + const error = await pyramidLaunch.validateIniPath(folder, '', 'x'); expect(error).to.be.length.greaterThan(1); }); - test('Validation of path should return errors if resolved path is ini', async () => { + test('Validation of path should not return errors if resolved path is ini', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - provider.resolveVariables = () => 'xyz.ini'; - - when(fs.fileExists('xyz.ini')).thenResolve(true); - const error = await provider.validateIniPath(folder, '', 'x'); + pathExistsStub.withArgs('xyz.ini').resolves(true); + const error = await pyramidLaunch.validateIniPath(folder, '', 'xyz.ini'); expect(error).to.be.equal(undefined, 'should not have errors'); }); test('Launch JSON with valid ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve('xyz.ini'); - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, type: DebuggerTypeName, request: 'launch', module: 'pyramid.scripts.pserve', - args: ['xyz.ini'], + args: ['${workspaceFolder}-development.ini'], pyramid: true, jinja: true, justMyCode: true, @@ -146,11 +118,10 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with selected ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve('hello'); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, @@ -168,14 +139,13 @@ suite('Debugging - Configuration Provider Pyramid', () => { test('Launch JSON with default ini path', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - provider.getDevelopmentIniPath = () => Promise.resolve(undefined); const workspaceFolderToken = '${workspaceFolder}'; const defaultIni = `${workspaceFolderToken}-development.ini`; - when(pathUtils.separator).thenReturn('-'); + pathSeparatorStub.value('-'); when(input.showInputBox(anything())).thenResolve(); - await provider.buildConfiguration(instance(input), state); + await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); const config = { name: DebugConfigStrings.pyramid.snippet.name, diff --git a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts index 7bf88543bdad..323cda94a1eb 100644 --- a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts +++ b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts @@ -5,34 +5,29 @@ import { expect } from 'chai'; import * as path from 'path'; +import * as sinon from 'sinon'; import { anything, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { RemoteAttachDebugConfigurationProvider } from '../../../../../client/debugger/extension/configuration/providers/remoteAttach'; +import * as configuration from '../../../../../client/debugger/extension/configuration/utils/configuration'; +import * as remoteAttach from '../../../../../client/debugger/extension/configuration/providers/remoteAttach'; import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import { AttachRequestArguments } from '../../../../../client/debugger/types'; suite('Debugging - Configuration Provider Remote Attach', () => { - let provider: TestRemoteAttachDebugConfigurationProvider; let input: MultiStepInput; - class TestRemoteAttachDebugConfigurationProvider extends RemoteAttachDebugConfigurationProvider { - public async configurePort( - i: MultiStepInput, - config: Partial, - ) { - return super.configurePort(i, config); - } - } + setup(() => { input = mock>(MultiStepInput); - provider = new TestRemoteAttachDebugConfigurationProvider(); + }); + teardown(() => { + sinon.restore(); }); test('Configure port will display prompt', async () => { when(input.showInputBox(anything())).thenResolve(); - await provider.configurePort(instance(input), {}); + await configuration.configurePort(instance(input), {}); verify(input.showInputBox(anything())).once(); }); @@ -40,7 +35,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('xyz'); - await provider.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -49,7 +44,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve(); - await provider.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 5678 } }); @@ -58,7 +53,7 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const config: { connect?: { port?: number } } = {}; when(input.showInputBox(anything())).thenResolve('1234'); - await provider.configurePort(instance(input), config); + await configuration.configurePort(instance(input), config); verify(input.showInputBox(anything())).once(); expect(config).to.be.deep.equal({ connect: { port: 1234 } }); @@ -68,12 +63,12 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const state = { config: {}, folder }; let portConfigured = false; when(input.showInputBox(anything())).thenResolve(); - provider.configurePort = () => { + + sinon.stub(configuration, 'configurePort').callsFake(async () => { portConfigured = true; - return Promise.resolve(); - }; + }); - const configurePort = await provider.buildConfiguration(instance(input), state); + const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); if (configurePort) { await configurePort!(input, state); } @@ -103,13 +98,11 @@ suite('Debugging - Configuration Provider Remote Attach', () => { const state = { config: {}, folder }; let portConfigured = false; when(input.showInputBox(anything())).thenResolve('Hello'); - provider.configurePort = (_, cfg) => { + sinon.stub(configuration, 'configurePort').callsFake(async (_, cfg) => { portConfigured = true; cfg.connect!.port = 9999; - return Promise.resolve(); - }; - - const configurePort = await provider.buildConfiguration(instance(input), state); + }); + const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); if (configurePort) { await configurePort(input, state); } diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index 6dc27ba2ed9c..28ae70d7bf98 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -17,31 +17,16 @@ import { LaunchJsonCompletionProvider } from '../../../client/debugger/extension import { InterpreterPathCommand } from '../../../client/debugger/extension/configuration/launch.json/interpreterPathCommand'; import { LaunchJsonReader } from '../../../client/debugger/extension/configuration/launch.json/launchJsonReader'; import { LaunchJsonUpdaterService } from '../../../client/debugger/extension/configuration/launch.json/updaterService'; -import { DjangoLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/djangoLaunch'; -import { FastAPILaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/fastapiLaunch'; -import { FileLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/fileLaunch'; -import { FlaskLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/flaskLaunch'; -import { ModuleLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/moduleLaunch'; -import { PidAttachDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/pidAttach'; -import { DebugConfigurationProviderFactory } from '../../../client/debugger/extension/configuration/providers/providerFactory'; -import { PyramidLaunchDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/pyramidLaunch'; -import { RemoteAttachDebugConfigurationProvider } from '../../../client/debugger/extension/configuration/providers/remoteAttach'; import { AttachConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/attach'; import { LaunchConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/launch'; -import { - IDebugConfigurationProviderFactory, - IDebugConfigurationResolver, - ILaunchJsonReader, -} from '../../../client/debugger/extension/configuration/types'; +import { IDebugConfigurationResolver, ILaunchJsonReader } from '../../../client/debugger/extension/configuration/types'; import { DebugCommands } from '../../../client/debugger/extension/debugCommands'; import { ChildProcessAttachEventHandler } from '../../../client/debugger/extension/hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from '../../../client/debugger/extension/hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from '../../../client/debugger/extension/hooks/types'; import { registerTypes } from '../../../client/debugger/extension/serviceRegistry'; import { - DebugConfigurationType, IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, IDebugConfigurationService, IDebuggerBanner, IDebugSessionLoggingFactory, @@ -123,69 +108,6 @@ suite('Debugging - Service Registry', () => { 'attach', ), ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProviderFactory, - DebugConfigurationProviderFactory, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FileLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFile, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - DjangoLaunchDebugConfigurationProvider, - DebugConfigurationType.launchDjango, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FastAPILaunchDebugConfigurationProvider, - DebugConfigurationType.launchFastAPI, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - FlaskLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFlask, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - RemoteAttachDebugConfigurationProvider, - DebugConfigurationType.remoteAttach, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - ModuleLaunchDebugConfigurationProvider, - DebugConfigurationType.launchModule, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - PyramidLaunchDebugConfigurationProvider, - DebugConfigurationType.launchPyramid, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationProvider, - PidAttachDebugConfigurationProvider, - DebugConfigurationType.pidAttach, - ), - ).once(); - verify( serviceManager.addSingleton( IExtensionSingleActivationService,