diff --git a/README.md b/README.md index 8029aa096587..3a56a09f73ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Python extension for Visual Studio Code -A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported versions](https://devguide.python.org/#status-of-python-branches) of the language: >=3.7), including features such as IntelliSense (Pylance), linting, debugging, code navigation, code formatting, refactoring, variable explorer, test explorer, and more! +A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported versions](https://devguide.python.org/#status-of-python-branches) of the language: >=3.7), including features such as IntelliSense (Pylance), linting, debugging (Python Debugger), code navigation, code formatting, refactoring, variable explorer, test explorer, and more! ## Support for [vscode.dev](https://vscode.dev/) diff --git a/gulpfile.js b/gulpfile.js index 5336deafee29..da46943f7335 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -98,7 +98,7 @@ async function addExtensionPackDependencies() { // extension dependencies need not be installed during development const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8'); const packageJson = JSON.parse(packageJsonContents); - packageJson.extensionPack = ['ms-python.vscode-pylance'].concat( + packageJson.extensionPack = ['ms-python.vscode-pylance', 'ms-python.debugpy'].concat( packageJson.extensionPack ? packageJson.extensionPack : [], ); // Remove potential duplicates. diff --git a/package.json b/package.json index 1976a7076f7e..fddd5974d0f3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "python", "displayName": "Python", - "description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), code formatting, refactoring, unit tests, and more.", + "description": "IntelliSense (Pylance), Linting, Debugging (Python Debugger), code formatting, refactoring, unit tests, and more.", "version": "2023.23.0-dev", "featureFlags": { "usingNewInterpreterStorage": true @@ -66,7 +66,6 @@ "activationEvents": [ "onDebugInitialConfigurations", "onLanguage:python", - "onDebugDynamicConfigurations:python", "onDebugResolve:python", "workspaceContains:mspythonconfig.json", "workspaceContains:pyproject.toml", @@ -388,12 +387,6 @@ "icon": "$(play)", "title": "%python.command.python.execInDedicatedTerminal.title%" }, - { - "category": "Python", - "command": "python.debugInTerminal", - "icon": "$(debug-alt)", - "title": "%python.command.python.debugInTerminal.title%" - }, { "category": "Python", "command": "python.execSelectionInDjangoShell", @@ -1323,13 +1316,6 @@ "title": "%python.command.python.execInDedicatedTerminal.title%", "when": "false" }, - { - "category": "Python", - "command": "python.debugInTerminal", - "icon": "$(debug-alt)", - "title": "%python.command.python.debugInTerminal.title%", - "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" - }, { "category": "Python", "command": "python.execSelectionInDjangoShell", @@ -1462,12 +1448,6 @@ "group": "navigation@0", "title": "%python.command.python.execInDedicatedTerminal.title%", "when": "resourceLangId == python && !isInDiffEditor && !virtualWorkspace && shellExecutionSupported" - }, - { - "command": "python.debugInTerminal", - "group": "navigation@1", - "title": "%python.command.python.debugInTerminal.title%", - "when": "resourceLangId == python && !isInDiffEditor && !virtualWorkspace && shellExecutionSupported" } ], "explorer/context": [ diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts index 8d9b765939c9..acf460b88625 100644 --- a/src/client/application/diagnostics/serviceRegistry.ts +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -11,10 +11,6 @@ import { EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId, } from './checks/envPathVariable'; -import { - InvalidLaunchJsonDebuggerService, - InvalidLaunchJsonDebuggerServiceId, -} from './checks/invalidLaunchJsonDebugger'; import { InvalidPythonPathInDebuggerService, InvalidPythonPathInDebuggerServiceId, @@ -59,11 +55,6 @@ export function registerTypes(serviceManager: IServiceManager): void { EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId, ); - serviceManager.addSingleton( - IDiagnosticsService, - InvalidLaunchJsonDebuggerService, - InvalidLaunchJsonDebuggerServiceId, - ); serviceManager.addSingleton( IDiagnosticsService, InvalidPythonInterpreterService, diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts deleted file mode 100644 index 80a1e3a8a8c4..000000000000 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { cloneDeep } from 'lodash'; -import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; -import { DebugConfigStrings } from '../../../common/utils/localize'; -import { - IMultiStepInputFactory, - InputStep, - IQuickPickParameters, - MultiStepInput, -} from '../../../common/utils/multiStepInput'; -import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } 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 { - private cacheDebugConfig: DebugConfiguration | undefined = undefined; - - constructor( - @inject(IDebugConfigurationResolver) - @named('attach') - private readonly attachResolver: IDebugConfigurationResolver, - @inject(IDebugConfigurationResolver) - @named('launch') - private readonly launchResolver: IDebugConfigurationResolver, - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, - ) {} - - public async provideDebugConfigurations( - folder: WorkspaceFolder | undefined, - token?: CancellationToken, - ): Promise { - const config: Partial = {}; - const state = { config, folder, token }; - - // Disabled until configuration issues are addressed by VS Code. See #4007 - const multiStep = this.multiStepFactory.create(); - await multiStep.run((input, s) => PythonDebugConfigurationService.pickDebugConfiguration(input, s), state); - - if (Object.keys(state.config).length !== 0) { - return [state.config as DebugConfiguration]; - } - return undefined; - } - - public async resolveDebugConfiguration( - folder: WorkspaceFolder | undefined, - debugConfiguration: DebugConfiguration, - token?: CancellationToken, - ): Promise { - if (debugConfiguration.request === 'attach') { - return this.attachResolver.resolveDebugConfiguration( - folder, - debugConfiguration as AttachRequestArguments, - token, - ); - } - if (debugConfiguration.request === 'test') { - // `"request": "test"` is now deprecated. But some users might have it in their - // launch config. We get here if they triggered it using F5 or start with debugger. - throw Error( - 'This configuration can only be used by the test debugging commands. `"request": "test"` is deprecated, please keep as `"request": "launch"` and add `"purpose": ["debug-test"]` instead.', - ); - } else { - if (Object.keys(debugConfiguration).length === 0) { - if (this.cacheDebugConfig) { - debugConfiguration = cloneDeep(this.cacheDebugConfig); - } else { - const configs = await this.provideDebugConfigurations(folder, token); - if (configs === undefined) { - return undefined; - } - if (Array.isArray(configs) && configs.length === 1) { - // eslint-disable-next-line prefer-destructuring - debugConfiguration = configs[0]; - } - this.cacheDebugConfig = cloneDeep(debugConfiguration); - } - } - return this.launchResolver.resolveDebugConfiguration( - folder, - debugConfiguration as LaunchRequestArguments, - token, - ); - } - } - - public async resolveDebugConfigurationWithSubstitutedVariables( - folder: WorkspaceFolder | undefined, - debugConfiguration: DebugConfiguration, - token?: CancellationToken, - ): Promise { - function resolve(resolver: IDebugConfigurationResolver) { - return resolver.resolveDebugConfigurationWithSubstitutedVariables(folder, debugConfiguration as T, token); - } - return debugConfiguration.request === 'attach' ? resolve(this.attachResolver) : resolve(this.launchResolver); - } - - // eslint-disable-next-line consistent-return - protected static async pickDebugConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ): Promise | void> { - type DebugConfigurationQuickPickItemFunc = ( - input: MultiStepInput, - state: DebugConfigurationState, - ) => Promise>; - type DebugConfigurationQuickPickItem = QuickPickItem & { - type: DebugConfigurationType; - func: DebugConfigurationQuickPickItemFunc; - }; - const items: DebugConfigurationQuickPickItem[] = [ - { - func: buildFileLaunchDebugConfiguration, - label: DebugConfigStrings.file.selectConfiguration.label, - type: DebugConfigurationType.launchFile, - description: DebugConfigStrings.file.selectConfiguration.description, - }, - { - func: buildModuleLaunchConfiguration, - label: DebugConfigStrings.module.selectConfiguration.label, - type: DebugConfigurationType.launchModule, - description: DebugConfigStrings.module.selectConfiguration.description, - }, - { - func: buildRemoteAttachConfiguration, - label: DebugConfigStrings.attach.selectConfiguration.label, - type: DebugConfigurationType.remoteAttach, - description: DebugConfigStrings.attach.selectConfiguration.description, - }, - { - func: buildPidAttachConfiguration, - label: DebugConfigStrings.attachPid.selectConfiguration.label, - type: DebugConfigurationType.pidAttach, - description: DebugConfigStrings.attachPid.selectConfiguration.description, - }, - { - func: buildDjangoLaunchDebugConfiguration, - label: DebugConfigStrings.django.selectConfiguration.label, - type: DebugConfigurationType.launchDjango, - description: DebugConfigStrings.django.selectConfiguration.description, - }, - { - func: buildFastAPILaunchDebugConfiguration, - label: DebugConfigStrings.fastapi.selectConfiguration.label, - type: DebugConfigurationType.launchFastAPI, - description: DebugConfigStrings.fastapi.selectConfiguration.description, - }, - { - func: buildFlaskLaunchDebugConfiguration, - label: DebugConfigStrings.flask.selectConfiguration.label, - type: DebugConfigurationType.launchFlask, - description: DebugConfigStrings.flask.selectConfiguration.description, - }, - { - func: buildPyramidLaunchConfiguration, - label: DebugConfigStrings.pyramid.selectConfiguration.label, - type: DebugConfigurationType.launchPyramid, - description: DebugConfigStrings.pyramid.selectConfiguration.description, - }, - ]; - const debugConfigurations = new Map(); - for (const config of items) { - debugConfigurations.set(config.type, config.func); - } - - state.config = {}; - const pick = await input.showQuickPick< - DebugConfigurationQuickPickItem, - IQuickPickParameters - >({ - title: DebugConfigStrings.selectConfiguration.title, - placeholder: DebugConfigStrings.selectConfiguration.placeholder, - activeItem: items[0], - items, - }); - if (pick) { - const pickedDebugConfiguration = debugConfigurations.get(pick.type)!; - return pickedDebugConfiguration(input, state); - } - } -} diff --git a/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts b/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts deleted file mode 100644 index e79f201d9367..000000000000 --- a/src/client/debugger/extension/configuration/dynamicdebugConfigurationService.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { injectable } from 'inversify'; -import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode'; -import { IDynamicDebugConfigurationService } from '../types'; -import { DebuggerTypeName } from '../../constants'; -import { asyncFilter } from '../../../common/utils/arrayUtils'; -import { replaceAll } from '../../../common/stringUtils'; - -const workspaceFolderToken = '${workspaceFolder}'; - -@injectable() -export class DynamicPythonDebugConfigurationService implements IDynamicDebugConfigurationService { - // eslint-disable-next-line class-methods-use-this - public async provideDebugConfigurations( - folder: WorkspaceFolder, - _token?: CancellationToken, - ): Promise { - const providers = []; - - providers.push({ - name: 'Python: File', - type: DebuggerTypeName, - request: 'launch', - program: '${file}', - justMyCode: true, - }); - - const djangoManagePath = await DynamicPythonDebugConfigurationService.getDjangoPath(folder); - if (djangoManagePath) { - providers.push({ - name: 'Python: Django', - type: DebuggerTypeName, - request: 'launch', - program: `${workspaceFolderToken}${path.sep}${djangoManagePath}`, - args: ['runserver'], - django: true, - justMyCode: true, - }); - } - - const flaskPath = await DynamicPythonDebugConfigurationService.getFlaskPath(folder); - if (flaskPath) { - providers.push({ - name: 'Python: Flask', - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: path.relative(folder.uri.fsPath, flaskPath), - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }); - } - - let fastApiPath = await DynamicPythonDebugConfigurationService.getFastApiPath(folder); - if (fastApiPath) { - fastApiPath = replaceAll(path.relative(folder.uri.fsPath, fastApiPath), path.sep, '.').replace('.py', ''); - providers.push({ - name: 'Python: FastAPI', - type: DebuggerTypeName, - request: 'launch', - module: 'uvicorn', - args: [`${fastApiPath}:app`, '--reload'], - jinja: true, - justMyCode: true, - }); - } - - return providers; - } - - private static async getDjangoPath(folder: WorkspaceFolder) { - const regExpression = /execute_from_command_line\(/; - const possiblePaths = await DynamicPythonDebugConfigurationService.getPossiblePaths( - folder, - ['manage.py', '*/manage.py', 'app.py', '*/app.py'], - regExpression, - ); - return possiblePaths.length ? path.relative(folder.uri.fsPath, possiblePaths[0]) : null; - } - - private static async getFastApiPath(folder: WorkspaceFolder) { - const regExpression = /app\s*=\s*FastAPI\(/; - const fastApiPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths( - folder, - ['main.py', 'app.py', '*/main.py', '*/app.py', '*/*/main.py', '*/*/app.py'], - regExpression, - ); - - return fastApiPaths.length ? fastApiPaths[0] : null; - } - - private static async getFlaskPath(folder: WorkspaceFolder) { - const regExpression = /app(?:lication)?\s*=\s*(?:flask\.)?Flask\(|def\s+(?:create|make)_app\(/; - const flaskPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths( - folder, - ['__init__.py', 'app.py', 'wsgi.py', '*/__init__.py', '*/app.py', '*/wsgi.py'], - regExpression, - ); - - return flaskPaths.length ? flaskPaths[0] : null; - } - - private static async getPossiblePaths( - folder: WorkspaceFolder, - globPatterns: string[], - regex: RegExp, - ): Promise { - const foundPathsPromises = (await Promise.allSettled( - globPatterns.map( - async (pattern): Promise => - (await fs.pathExists(path.join(folder.uri.fsPath, pattern))) - ? [path.join(folder.uri.fsPath, pattern)] - : [], - ), - )) as { status: string; value: [] }[]; - const possiblePaths: string[] = []; - foundPathsPromises.forEach((result) => possiblePaths.push(...result.value)); - const finalPaths = await asyncFilter(possiblePaths, async (possiblePath) => - regex.exec((await fs.readFile(possiblePath)).toString()), - ); - - return finalPaths; - } -} diff --git a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts b/src/client/debugger/extension/configuration/launch.json/completionProvider.ts deleted file mode 100644 index c3b243fe9065..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { getLocation } from 'jsonc-parser'; -import * as path from 'path'; -import { - CancellationToken, - CompletionItem, - CompletionItemKind, - CompletionItemProvider, - Position, - SnippetString, - TextDocument, -} from 'vscode'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { ILanguageService } from '../../../../common/application/types'; -import { IDisposableRegistry } from '../../../../common/types'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; - -const configurationNodeName = 'configurations'; -enum JsonLanguages { - json = 'json', - jsonWithComments = 'jsonc', -} - -@injectable() -export class LaunchJsonCompletionProvider implements CompletionItemProvider, IExtensionSingleActivationService { - public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; - - constructor( - @inject(ILanguageService) private readonly languageService: ILanguageService, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, - ) {} - - public async activate(): Promise { - this.disposableRegistry.push( - this.languageService.registerCompletionItemProvider({ language: JsonLanguages.json }, this), - ); - this.disposableRegistry.push( - this.languageService.registerCompletionItemProvider({ language: JsonLanguages.jsonWithComments }, this), - ); - } - - // eslint-disable-next-line class-methods-use-this - public async provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - ): Promise { - if (!LaunchJsonCompletionProvider.canProvideCompletions(document, position)) { - return []; - } - - return [ - { - command: { - command: 'python.SelectAndInsertDebugConfiguration', - title: DebugConfigStrings.launchJsonCompletions.description, - arguments: [document, position, token], - }, - documentation: DebugConfigStrings.launchJsonCompletions.description, - sortText: 'AAAA', - preselect: true, - kind: CompletionItemKind.Enum, - label: DebugConfigStrings.launchJsonCompletions.label, - insertText: new SnippetString(), - }, - ]; - } - - public static canProvideCompletions(document: TextDocument, position: Position): boolean { - if (path.basename(document.uri.fsPath) !== 'launch.json') { - return false; - } - const location = getLocation(document.getText(), document.offsetAt(position)); - // Cursor must be inside the configurations array and not in any nested items. - // Hence path[0] = array, path[1] = array element index. - return location.path[0] === configurationNodeName && location.path.length === 2; - } -} diff --git a/src/client/debugger/extension/configuration/launch.json/updaterService.ts b/src/client/debugger/extension/configuration/launch.json/updaterService.ts deleted file mode 100644 index b95749040f3c..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/updaterService.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { IDisposableRegistry } from '../../../../common/types'; -import { registerCommand } from '../../../../common/vscodeApis/commandApis'; -import { IDebugConfigurationService } from '../../types'; -import { LaunchJsonUpdaterServiceHelper } from './updaterServiceHelper'; - -@injectable() -export class LaunchJsonUpdaterService implements IExtensionSingleActivationService { - public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; - - constructor( - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, - @inject(IDebugConfigurationService) private readonly configurationProvider: IDebugConfigurationService, - ) {} - - public async activate(): Promise { - const handler = new LaunchJsonUpdaterServiceHelper(this.configurationProvider); - this.disposableRegistry.push( - registerCommand('python.SelectAndInsertDebugConfiguration', handler.selectAndInsertDebugConfig, handler), - ); - } -} diff --git a/src/client/debugger/extension/configuration/launch.json/updaterServiceHelper.ts b/src/client/debugger/extension/configuration/launch.json/updaterServiceHelper.ts deleted file mode 100644 index bc0820fa188f..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/updaterServiceHelper.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { createScanner, parse, SyntaxKind } from 'jsonc-parser'; -import { CancellationToken, DebugConfiguration, Position, Range, TextDocument, WorkspaceEdit } from 'vscode'; -import { noop } from '../../../../common/utils/misc'; -import { executeCommand } from '../../../../common/vscodeApis/commandApis'; -import { getActiveTextEditor } from '../../../../common/vscodeApis/windowApis'; -import { applyEdit, getWorkspaceFolder } from '../../../../common/vscodeApis/workspaceApis'; -import { captureTelemetry } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { IDebugConfigurationService } from '../../types'; - -type PositionOfCursor = 'InsideEmptyArray' | 'BeforeItem' | 'AfterItem'; -type PositionOfComma = 'BeforeCursor'; - -export class LaunchJsonUpdaterServiceHelper { - constructor(private readonly configurationProvider: IDebugConfigurationService) {} - - @captureTelemetry(EventName.DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON) - public async selectAndInsertDebugConfig( - document: TextDocument, - position: Position, - token: CancellationToken, - ): Promise { - const activeTextEditor = getActiveTextEditor(); - if (activeTextEditor && activeTextEditor.document === document) { - const folder = getWorkspaceFolder(document.uri); - const configs = await this.configurationProvider.provideDebugConfigurations!(folder, token); - - if (!token.isCancellationRequested && Array.isArray(configs) && configs.length > 0) { - // Always use the first available debug configuration. - await LaunchJsonUpdaterServiceHelper.insertDebugConfiguration(document, position, configs[0]); - } - } - } - - /** - * Inserts the debug configuration into the document. - * Invokes the document formatter to ensure JSON is formatted nicely. - * @param {TextDocument} document - * @param {Position} position - * @param {DebugConfiguration} config - * @returns {Promise} - * @memberof LaunchJsonCompletionItemProvider - */ - public static async insertDebugConfiguration( - document: TextDocument, - position: Position, - config: DebugConfiguration, - ): Promise { - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document, - position, - ); - if (!cursorPosition) { - return; - } - const commaPosition = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document, position) - ? 'BeforeCursor' - : undefined; - const formattedJson = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, cursorPosition, commaPosition); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.insert(document.uri, position, formattedJson); - await applyEdit(workspaceEdit); - executeCommand('editor.action.formatDocument').then(noop, noop); - } - - /** - * Gets the string representation of the debug config for insertion in the document. - * Adds necessary leading or trailing commas (remember the text is added into an array). - * @param {DebugConfiguration} config - * @param {PositionOfCursor} cursorPosition - * @param {PositionOfComma} [commaPosition] - * @returns - * @memberof LaunchJsonCompletionItemProvider - */ - public static getTextForInsertion( - config: DebugConfiguration, - cursorPosition: PositionOfCursor, - commaPosition?: PositionOfComma, - ): string { - const json = JSON.stringify(config); - if (cursorPosition === 'AfterItem') { - // If we already have a comma immediatley before the cursor, then no need of adding a comma. - return commaPosition === 'BeforeCursor' ? json : `,${json}`; - } - if (cursorPosition === 'BeforeItem') { - return `${json},`; - } - return json; - } - - public static getCursorPositionInConfigurationsArray( - document: TextDocument, - position: Position, - ): PositionOfCursor | undefined { - if (LaunchJsonUpdaterServiceHelper.isConfigurationArrayEmpty(document)) { - return 'InsideEmptyArray'; - } - const scanner = createScanner(document.getText(), true); - scanner.setPosition(document.offsetAt(position)); - const nextToken = scanner.scan(); - if (nextToken === SyntaxKind.CommaToken || nextToken === SyntaxKind.CloseBracketToken) { - return 'AfterItem'; - } - if (nextToken === SyntaxKind.OpenBraceToken) { - return 'BeforeItem'; - } - return undefined; - } - - public static isConfigurationArrayEmpty(document: TextDocument): boolean { - const configuration = parse(document.getText(), [], { allowTrailingComma: true, disallowComments: false }) as { - configurations: []; - }; - return ( - !configuration || !Array.isArray(configuration.configurations) || configuration.configurations.length === 0 - ); - } - - public static isCommaImmediatelyBeforeCursor(document: TextDocument, position: Position): boolean { - const line = document.lineAt(position.line); - // Get text from start of line until the cursor. - const currentLine = document.getText(new Range(line.range.start, position)); - if (currentLine.trim().endsWith(',')) { - return true; - } - // If there are other characters, then don't bother. - if (currentLine.trim().length !== 0) { - return false; - } - - // Keep walking backwards until we hit a non-comma character or a comm character. - let startLineNumber = position.line - 1; - while (startLineNumber > 0) { - const lineText = document.lineAt(startLineNumber).text; - if (lineText.trim().endsWith(',')) { - return true; - } - // If there are other characters, then don't bother. - if (lineText.trim().length !== 0) { - return false; - } - startLineNumber -= 1; - } - return false; - } -} diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts deleted file mode 100644 index 4e1513ccb1ea..000000000000 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs-extra'; -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 } from '../../types'; -import { resolveVariables } from '../utils/common'; - -const workspaceFolderToken = '${workspaceFolder}'; - -export async function buildDjangoLaunchDebugConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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); -} - -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; - } - const resolvedPath = resolveVariables(selected, undefined, folder); - if (resolvedPath) { - if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { - return error; - } - } - return undefined; -} - -export async function getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { - if (!folder) { - return undefined; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${path.sep}manage.py`; - } - return undefined; -} diff --git a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts b/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts deleted file mode 100644 index 38a9b7ccf1a2..000000000000 --- a/src/client/debugger/extension/configuration/providers/fastapiLaunch.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { WorkspaceFolder } from 'vscode'; -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 } from '../../types'; - -export async function buildFastAPILaunchDebugConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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', '--reload'], - jinja: true, - justMyCode: true, - }; - - if (!application && config.args) { - 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[0] = `${path.basename(selectedPath, '.py').replace('/', '.')}:app`; - } - } - - 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 undefined; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'main.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return 'main.py'; - } - return undefined; -} diff --git a/src/client/debugger/extension/configuration/providers/fileLaunch.ts b/src/client/debugger/extension/configuration/providers/fileLaunch.ts deleted file mode 100644 index edda7ed7e22d..000000000000 --- a/src/client/debugger/extension/configuration/providers/fileLaunch.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from '../../types'; - -export async function buildFileLaunchDebugConfiguration( - _input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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.launchFile, - }); - 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 deleted file mode 100644 index d85258c800c6..000000000000 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { WorkspaceFolder } from 'vscode'; -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 } from '../../types'; - -export async function buildFlaskLaunchDebugConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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; - } - } - - 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 undefined; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return 'app.py'; - } - return undefined; -} diff --git a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts deleted file mode 100644 index 16787296ce7c..000000000000 --- a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from '../../types'; - -export async function buildModuleLaunchConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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 deleted file mode 100644 index fc0d66874470..000000000000 --- a/src/client/debugger/extension/configuration/providers/pidAttach.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType } from '../../types'; - -export async function buildPidAttachConfiguration( - _input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts deleted file mode 100644 index 315e204e7bf8..000000000000 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { l10n, WorkspaceFolder } from 'vscode'; -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 } from '../../types'; -import { resolveVariables } from '../utils/common'; - -const workspaceFolderToken = '${workspaceFolder}'; - -export async function buildPyramidLaunchConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, -): Promise { - 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, - }; - - if (!iniPath) { - const selectedIniPath = await input.showInputBox({ - title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title, - value: defaultIni, - prompt: l10n.t( - '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); -} - -export async function validateIniPath( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, -): Promise { - if (!folder) { - return undefined; - } - const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = resolveVariables(selected, undefined, folder); - if (resolvedPath) { - if (selected !== defaultValue && !fs.pathExists(resolvedPath)) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { - return error; - } - } - return undefined; -} - -export async function getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return undefined; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${path.sep}development.ini`; - } - return undefined; -} diff --git a/src/client/debugger/extension/configuration/providers/remoteAttach.ts b/src/client/debugger/extension/configuration/providers/remoteAttach.ts deleted file mode 100644 index a43c48b664af..000000000000 --- a/src/client/debugger/extension/configuration/providers/remoteAttach.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from '../../types'; -import { configurePort } from '../utils/configuration'; - -const defaultHost = 'localhost'; -const defaultPort = 5678; - -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: '.', - }, - ], - 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 (_) => configurePort(input, state.config); -} diff --git a/src/client/debugger/extension/configuration/utils/configuration.ts b/src/client/debugger/extension/configuration/utils/configuration.ts deleted file mode 100644 index 37fb500dbfdd..000000000000 --- a/src/client/debugger/extension/configuration/utils/configuration.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* 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/helpers/protocolParser.ts b/src/client/debugger/extension/helpers/protocolParser.ts deleted file mode 100644 index c0d1306a841b..000000000000 --- a/src/client/debugger/extension/helpers/protocolParser.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { EventEmitter } from 'events'; -import { injectable } from 'inversify'; -import { Readable } from 'stream'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { IProtocolParser } from '../types'; - -const PROTOCOL_START_INDENTIFIER = '\r\n\r\n'; - -type Listener = (...args: unknown[]) => void; - -/** - * Parsers the debugger Protocol messages and raises the following events: - * 1. 'data', message (for all protocol messages) - * 1. 'event_', message (for all protocol events) - * 1. 'request_', message (for all protocol requests) - * 1. 'response_', message (for all protocol responses) - * 1. '', message (for all protocol messages that are not events, requests nor responses) - * @export - * @class ProtocolParser - * @extends {EventEmitter} - * @implements {IProtocolParser} - */ -@injectable() -export class ProtocolParser implements IProtocolParser { - private rawData = Buffer.alloc(0); - - private contentLength = -1; - - private disposed = false; - - private stream?: Readable; - - private events: EventEmitter; - - constructor() { - this.events = new EventEmitter(); - } - - public dispose(): void { - if (this.stream) { - this.stream.removeListener('data', this.dataCallbackHandler); - this.stream = undefined; - } - } - - public connect(stream: Readable): void { - this.stream = stream; - stream.addListener('data', this.dataCallbackHandler); - } - - public on(event: string | symbol, listener: Listener): this { - this.events.on(event, listener); - return this; - } - - public once(event: string | symbol, listener: Listener): this { - this.events.once(event, listener); - return this; - } - - private dataCallbackHandler = (data: string | Buffer) => { - this.handleData(data as Buffer); - }; - - private dispatch(body: string): void { - const message = JSON.parse(body) as DebugProtocol.ProtocolMessage; - - switch (message.type) { - case 'event': { - const event = message as DebugProtocol.Event; - if (typeof event.event === 'string') { - this.events.emit(`${message.type}_${event.event}`, event); - } - break; - } - case 'request': { - const request = message as DebugProtocol.Request; - if (typeof request.command === 'string') { - this.events.emit(`${message.type}_${request.command}`, request); - } - break; - } - case 'response': { - const reponse = message as DebugProtocol.Response; - if (typeof reponse.command === 'string') { - this.events.emit(`${message.type}_${reponse.command}`, reponse); - } - break; - } - default: { - this.events.emit(`${message.type}`, message); - } - } - - this.events.emit('data', message); - } - - private handleData(data: Buffer): void { - if (this.disposed) { - return; - } - this.rawData = Buffer.concat([this.rawData, data]); - - // eslint-disable-next-line no-constant-condition - while (true) { - if (this.contentLength >= 0) { - if (this.rawData.length >= this.contentLength) { - const message = this.rawData.toString('utf8', 0, this.contentLength); - this.rawData = this.rawData.slice(this.contentLength); - this.contentLength = -1; - if (message.length > 0) { - this.dispatch(message); - } - // there may be more complete messages to process. - // eslint-disable-next-line no-continue - continue; - } - } else { - const idx = this.rawData.indexOf(PROTOCOL_START_INDENTIFIER); - if (idx !== -1) { - const header = this.rawData.toString('utf8', 0, idx); - const lines = header.split('\r\n'); - for (const line of lines) { - const pair = line.split(/: +/); - if (pair[0] === 'Content-Length') { - this.contentLength = +pair[1]; - } - } - this.rawData = this.rawData.slice(idx + PROTOCOL_START_INDENTIFIER.length); - // eslint-disable-next-line no-continue - continue; - } - } - break; - } - } -} diff --git a/src/client/debugger/extension/serviceRegistry.ts b/src/client/debugger/extension/serviceRegistry.ts index a8c5ae7bbfcc..b4baca5d9ce7 100644 --- a/src/client/debugger/extension/serviceRegistry.ts +++ b/src/client/debugger/extension/serviceRegistry.ts @@ -12,11 +12,6 @@ import { DebugSessionLoggingFactory } from './adapter/logging'; import { OutdatedDebuggerPromptFactory } from './adapter/outdatedDebuggerPrompt'; import { AttachProcessProviderFactory } from './attachQuickPick/factory'; import { IAttachProcessProviderFactory } from './attachQuickPick/types'; -import { PythonDebugConfigurationService } from './configuration/debugConfigurationService'; -import { DynamicPythonDebugConfigurationService } from './configuration/dynamicdebugConfigurationService'; -import { LaunchJsonCompletionProvider } from './configuration/launch.json/completionProvider'; -import { InterpreterPathCommand } from './configuration/launch.json/interpreterPathCommand'; -import { LaunchJsonUpdaterService } from './configuration/launch.json/updaterService'; import { AttachConfigurationResolver } from './configuration/resolvers/attach'; import { DebugEnvironmentVariablesHelper, IDebugEnvironmentVariablesService } from './configuration/resolvers/helper'; import { LaunchConfigurationResolver } from './configuration/resolvers/launch'; @@ -25,35 +20,9 @@ import { DebugCommands } from './debugCommands'; import { ChildProcessAttachEventHandler } from './hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from './hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './hooks/types'; -import { - IDebugAdapterDescriptorFactory, - IDebugConfigurationService, - IDebugSessionLoggingFactory, - IDynamicDebugConfigurationService, - IOutdatedDebuggerPromptFactory, -} from './types'; +import { IDebugAdapterDescriptorFactory, IDebugSessionLoggingFactory, IOutdatedDebuggerPromptFactory } from './types'; export function registerTypes(serviceManager: IServiceManager): void { - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonCompletionProvider, - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - InterpreterPathCommand, - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonUpdaterService, - ); - serviceManager.addSingleton( - IDebugConfigurationService, - PythonDebugConfigurationService, - ); - serviceManager.addSingleton( - IDynamicDebugConfigurationService, - DynamicPythonDebugConfigurationService, - ); serviceManager.addSingleton(IChildProcessAttachService, ChildProcessAttachService); serviceManager.addSingleton(IDebugSessionEventHandlers, ChildProcessAttachEventHandler); serviceManager.addSingleton>( diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index 2a304efae918..7fe77b26f731 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -3,45 +3,7 @@ 'use strict'; -import { Readable } from 'stream'; -import { - CancellationToken, - DebugAdapterDescriptorFactory, - DebugAdapterTrackerFactory, - DebugConfigurationProvider, - Disposable, - WorkspaceFolder, -} from 'vscode'; - -import { DebugConfigurationArguments } from '../types'; - -export const IDebugConfigurationService = Symbol('IDebugConfigurationService'); -export interface IDebugConfigurationService extends DebugConfigurationProvider {} - -export const IDynamicDebugConfigurationService = Symbol('IDynamicDebugConfigurationService'); -export interface IDynamicDebugConfigurationService extends DebugConfigurationProvider {} - -export type DebugConfigurationState = { - config: Partial; - folder?: WorkspaceFolder; - token?: CancellationToken; -}; - -export enum DebugConfigurationType { - launchFile = 'launchFile', - remoteAttach = 'remoteAttach', - launchDjango = 'launchDjango', - launchFastAPI = 'launchFastAPI', - launchFlask = 'launchFlask', - launchModule = 'launchModule', - launchPyramid = 'launchPyramid', - pidAttach = 'pidAttach', -} - -export enum PythonPathSource { - launchJson = 'launch.json', - settingsJson = 'settings.json', -} +import { DebugAdapterDescriptorFactory, DebugAdapterTrackerFactory } from 'vscode'; export const IDebugAdapterDescriptorFactory = Symbol('IDebugAdapterDescriptorFactory'); export interface IDebugAdapterDescriptorFactory extends DebugAdapterDescriptorFactory {} @@ -54,9 +16,7 @@ export const IOutdatedDebuggerPromptFactory = Symbol('IOutdatedDebuggerPromptFac export interface IOutdatedDebuggerPromptFactory extends DebugAdapterTrackerFactory {} -export const IProtocolParser = Symbol('IProtocolParser'); -export interface IProtocolParser extends Disposable { - connect(stream: Readable): void; - once(event: string | symbol, listener: (...args: unknown[]) => void): this; - on(event: string | symbol, listener: (...args: unknown[]) => void): this; +export enum PythonPathSource { + launchJson = 'launch.json', + settingsJson = 'settings.json', } diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 60e82fb04418..3e884cf8f64f 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -61,6 +61,7 @@ interface ICommonDebugArguments { pathMappings?: PathMapping[]; clientOS?: 'windows' | 'unix'; } + interface IKnownAttachDebugArguments extends ICommonDebugArguments { workspaceFolder?: string; customDebugger?: boolean; diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index c4b663fdba6d..7383129ef135 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,7 +3,7 @@ 'use strict'; -import { debug, DebugConfigurationProvider, DebugConfigurationProviderTriggerKind, languages, window } from 'vscode'; +import { languages, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationManager } from './activation/types'; @@ -22,9 +22,7 @@ import { IPathUtils, } from './common/types'; import { noop } from './common/utils/misc'; -import { DebuggerTypeName } from './debugger/constants'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; -import { IDebugConfigurationService, IDynamicDebugConfigurationService } from './debugger/extension/types'; import { IInterpreterService } from './interpreter/contracts'; import { getLanguageConfiguration } from './language/languageConfiguration'; import { ReplProvider } from './providers/replProvider'; @@ -46,7 +44,6 @@ import { DebugService } from './common/application/debugService'; import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; import { WorkspaceService } from './common/application/workspace'; -import { DynamicPythonDebugConfigurationService } from './debugger/extension/configuration/dynamicdebugConfigurationService'; import { IInterpreterQuickPick } from './interpreter/configuration/types'; import { registerAllCreateEnvironmentFeatures } from './pythonEnvironments/creation/registrations'; import { registerCreateEnvironmentTriggers } from './pythonEnvironments/creation/createEnvironmentTrigger'; @@ -151,7 +148,6 @@ async function activateLegacy(ext: ExtensionState): Promise { const handlers = serviceManager.getAll(IDebugSessionEventHandlers); const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); dispatcher.registerEventHandlers(); - const outputChannel = serviceManager.get(ILogOutputChannel); disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); @@ -168,21 +164,6 @@ async function activateLegacy(ext: ExtensionState): Promise { terminalProvider.initialize(window.activeTerminal).ignoreErrors(); disposables.push(terminalProvider); - serviceContainer - .getAll(IDebugConfigurationService) - .forEach((debugConfigProvider) => { - disposables.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider)); - }); - - // register a dynamic configuration provider for 'python' debug type - disposables.push( - debug.registerDebugConfigurationProvider( - DebuggerTypeName, - serviceContainer.get(IDynamicDebugConfigurationService), - DebugConfigurationProviderTriggerKind.Dynamic, - ), - ); - logAndNotifyOnLegacySettings(); registerCreateEnvironmentTriggers(disposables); initializePersistentStateForTriggers(ext.context); diff --git a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts b/src/client/interpreter/interpreterPathCommand.ts similarity index 80% rename from src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts rename to src/client/interpreter/interpreterPathCommand.ts index 21c8d0f1147b..8402374d50d6 100644 --- a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts +++ b/src/client/interpreter/interpreterPathCommand.ts @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { Commands } from '../../../../common/constants'; -import { IDisposable, IDisposableRegistry } from '../../../../common/types'; -import { registerCommand } from '../../../../common/vscodeApis/commandApis'; -import { IInterpreterService } from '../../../../interpreter/contracts'; - -@injectable() -export class InterpreterPathCommand implements IExtensionSingleActivationService { - public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; - - constructor( - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IDisposableRegistry) private readonly disposables: IDisposable[], - ) {} - - public async activate(): Promise { - this.disposables.push( - registerCommand(Commands.GetSelectedInterpreterPath, (args) => this._getSelectedInterpreterPath(args)), - ); - } - - public async _getSelectedInterpreterPath(args: { workspaceFolder: string } | string[]): Promise { - // If `launch.json` is launching this command, `args.workspaceFolder` carries the workspaceFolder - // If `tasks.json` is launching this command, `args[1]` carries the workspaceFolder - let workspaceFolder; - if ('workspaceFolder' in args) { - workspaceFolder = args.workspaceFolder; - } else if (args[1]) { - const [, second] = args; - workspaceFolder = second; - } else { - workspaceFolder = undefined; - } - - let workspaceFolderUri; - try { - workspaceFolderUri = workspaceFolder ? Uri.file(workspaceFolder) : undefined; - } catch (ex) { - workspaceFolderUri = undefined; - } - - return (await this.interpreterService.getActiveInterpreter(workspaceFolderUri))?.path ?? 'python'; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IExtensionSingleActivationService } from '../activation/types'; +import { Commands } from '../common/constants'; +import { IDisposable, IDisposableRegistry } from '../common/types'; +import { registerCommand } from '../common/vscodeApis/commandApis'; +import { IInterpreterService } from './contracts'; + +@injectable() +export class InterpreterPathCommand implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + constructor( + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IDisposableRegistry) private readonly disposables: IDisposable[], + ) {} + + public async activate(): Promise { + this.disposables.push( + registerCommand(Commands.GetSelectedInterpreterPath, (args) => this._getSelectedInterpreterPath(args)), + ); + } + + public async _getSelectedInterpreterPath(args: { workspaceFolder: string } | string[]): Promise { + // If `launch.json` is launching this command, `args.workspaceFolder` carries the workspaceFolder + // If `tasks.json` is launching this command, `args[1]` carries the workspaceFolder + let workspaceFolder; + if ('workspaceFolder' in args) { + workspaceFolder = args.workspaceFolder; + } else if (args[1]) { + const [, second] = args; + workspaceFolder = second; + } else { + workspaceFolder = undefined; + } + + let workspaceFolderUri; + try { + workspaceFolderUri = workspaceFolder ? Uri.file(workspaceFolder) : undefined; + } catch (ex) { + workspaceFolderUri = undefined; + } + + return (await this.interpreterService.getActiveInterpreter(workspaceFolderUri))?.path ?? 'python'; + } +} diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 422776bd5e43..fa44038ec717 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -29,6 +29,7 @@ import { IActivatedEnvironmentLaunch, IInterpreterDisplay, IInterpreterHelper, I import { InterpreterDisplay } from './display'; import { InterpreterLocatorProgressStatubarHandler } from './display/progressDisplay'; import { InterpreterHelper } from './helpers'; +import { InterpreterPathCommand } from './interpreterPathCommand'; import { InterpreterService } from './interpreterService'; import { ActivatedEnvironmentLaunch } from './virtualEnvs/activatedEnvLaunch'; import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; @@ -108,4 +109,8 @@ export function registerTypes(serviceManager: IServiceManager): void { IEnvironmentActivationService, EnvironmentActivationService, ); + serviceManager.addSingleton( + IExtensionSingleActivationService, + InterpreterPathCommand, + ); } diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index 9e29ef808d0d..072537619224 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -44,8 +44,6 @@ export enum EventName { DEBUGGER = 'DEBUGGER', DEBUGGER_ATTACH_TO_CHILD_PROCESS = 'DEBUGGER.ATTACH_TO_CHILD_PROCESS', DEBUGGER_ATTACH_TO_LOCAL_PROCESS = 'DEBUGGER.ATTACH_TO_LOCAL_PROCESS', - DEBUGGER_CONFIGURATION_PROMPTS = 'DEBUGGER.CONFIGURATION.PROMPTS', - DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON = 'DEBUGGER.CONFIGURATION.PROMPTS.IN.LAUNCH.JSON', // Python testing specific telemetry UNITTEST_CONFIGURING = 'UNITTEST.CONFIGURING', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index f9ed98eb3764..42a73fb06e07 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -11,7 +11,6 @@ import { AppinsightsKey, EXTENSION_ROOT_DIR, isTestExecution, isUnitTestExecutio import type { TerminalShellType } from '../common/terminal/types'; import { StopWatch } from '../common/utils/stopWatch'; import { isPromise } from '../common/utils/async'; -import { DebugConfigurationType } from '../debugger/extension/types'; import { ConsoleType, TriggerType } from '../debugger/types'; import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; import { @@ -366,7 +365,6 @@ export interface IEventNamePropertyMapping { "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, "trigger" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "paulacamargo25" }, "console" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "paulacamargo25" } - } */ [EventName.DEBUG_SESSION_ERROR]: { @@ -615,66 +613,6 @@ export interface IEventNamePropertyMapping { "debugger.attach_to_local_process" : { "owner": "paulacamargo25" } */ [EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS]: never | undefined; - /** - * Telemetry sent after building configuration for debugger - */ - /* __GDPR__ - "debugger.configuration.prompts" : { - "configurationtype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, - "autodetecteddjangomanagepypath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, - "autodetectedpyramidinipath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, - "autodetectedfastapimainpypath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, - "autodetectedflaskapppypath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" }, - "manuallyenteredavalue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "paulacamargo25" } - } - */ - - [EventName.DEBUGGER_CONFIGURATION_PROMPTS]: { - /** - * The type of debug configuration to build configuration for - * - * @type {DebugConfigurationType} - */ - configurationType: DebugConfigurationType; - /** - * Carries `true` if we are able to auto-detect manage.py path for Django, `false` otherwise - * - * @type {boolean} - */ - autoDetectedDjangoManagePyPath?: boolean; - /** - * Carries `true` if we are able to auto-detect .ini file path for Pyramid, `false` otherwise - * - * @type {boolean} - */ - autoDetectedPyramidIniPath?: boolean; - /** - * Carries `true` if we are able to auto-detect main.py path for FastAPI, `false` otherwise - * - * @type {boolean} - */ - autoDetectedFastAPIMainPyPath?: boolean; - /** - * Carries `true` if we are able to auto-detect app.py path for Flask, `false` otherwise - * - * @type {boolean} - */ - autoDetectedFlaskAppPyPath?: boolean; - /** - * Carries `true` if user manually entered the required path for the app - * (path to `manage.py` for Django, path to `.ini` for Pyramid, path to `app.py` for Flask), `false` otherwise - * - * @type {boolean} - */ - manuallyEnteredAValue?: boolean; - }; - /** - * Telemetry event sent when providing completion provider in launch.json. It is sent just *after* inserting the completion. - */ - /* __GDPR__ - "debugger.configuration.prompts.in.launch.json" : { "owner": "paulacamargo25" } - */ - [EventName.DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON]: never | undefined; /** * Telemetry event sent with details of actions when invoking a diagnostic command */ diff --git a/src/test/application/diagnostics/checks/invalidLaunchJsonDebugger.unit.test.ts b/src/test/application/diagnostics/checks/invalidLaunchJsonDebugger.unit.test.ts deleted file mode 100644 index d4eefd69dd5f..000000000000 --- a/src/test/application/diagnostics/checks/invalidLaunchJsonDebugger.unit.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as TypeMoq from 'typemoq'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; -import { - InvalidLaunchJsonDebuggerDiagnostic, - InvalidLaunchJsonDebuggerService, -} from '../../../../client/application/diagnostics/checks/invalidLaunchJsonDebugger'; -import { IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; -import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; -import { MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; -import { - IDiagnostic, - IDiagnosticHandlerService, - IDiagnosticsService, -} from '../../../../client/application/diagnostics/types'; -import { IWorkspaceService } from '../../../../client/common/application/types'; -import { IFileSystem } from '../../../../client/common/platform/types'; -import { Diagnostics } from '../../../../client/common/utils/localize'; -import { IServiceContainer } from '../../../../client/ioc/types'; - -suite('Application Diagnostics - Checks if launch.json is invalid', () => { - let serviceContainer: TypeMoq.IMock; - let diagnosticService: IDiagnosticsService; - let commandFactory: TypeMoq.IMock; - let fs: TypeMoq.IMock; - let workspaceService: TypeMoq.IMock; - let baseWorkspaceService: TypeMoq.IMock; - let messageHandler: TypeMoq.IMock>; - let workspaceFolder: WorkspaceFolder; - - setup(() => { - workspaceFolder = { uri: Uri.parse('full/path/to/workspace'), name: '', index: 0 }; - serviceContainer = TypeMoq.Mock.ofType(); - commandFactory = TypeMoq.Mock.ofType(); - fs = TypeMoq.Mock.ofType(); - messageHandler = TypeMoq.Mock.ofType>(); - workspaceService = TypeMoq.Mock.ofType(); - baseWorkspaceService = TypeMoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IWorkspaceService))) - .returns(() => baseWorkspaceService.object); - - diagnosticService = new (class extends InvalidLaunchJsonDebuggerService { - public _clear() { - while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { - BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); - } - } - public async fixLaunchJson(code: DiagnosticCodes) { - await super.fixLaunchJson(code); - } - })(serviceContainer.object, fs.object, [], workspaceService.object, messageHandler.object); - (diagnosticService as any)._clear(); - }); - - test('Can handle all InvalidLaunchJsonDebugger diagnostics', async () => { - for (const code of [ - DiagnosticCodes.InvalidDebuggerTypeDiagnostic, - DiagnosticCodes.JustMyCodeDiagnostic, - DiagnosticCodes.ConsoleTypeDiagnostic, - ]) { - const diagnostic = TypeMoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => code) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const canHandle = await diagnosticService.canHandle(diagnostic.object); - expect(canHandle).to.be.equal(true, `Should be able to handle ${code}`); - diagnostic.verifyAll(); - } - }); - - test('Can not handle non-InvalidLaunchJsonDebugger diagnostics', async () => { - const diagnostic = TypeMoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => 'Something Else' as any) - .verifiable(TypeMoq.Times.atLeastOnce()); - - const canHandle = await diagnosticService.canHandle(diagnostic.object); - expect(canHandle).to.be.equal(false, 'Invalid value'); - diagnostic.verifyAll(); - }); - - test('Should return empty diagnostics if there are no workspace folders', async () => { - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal([]); - workspaceService.verifyAll(); - }); - - test('Should return empty diagnostics if file launch.json does not exist', async () => { - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - workspaceService - .setup((w) => w.getWorkspaceFolder(undefined)) - .returns(() => undefined) - .verifiable(TypeMoq.Times.never()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal([]); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return empty diagnostics if file launch.json does not contain strings "pythonExperimental" and "debugStdLib" ', async () => { - const fileContents = 'Hello I am launch.json, although I am not very jsony'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal([]); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return InvalidDebuggerTypeDiagnostic if file launch.json contains string "pythonExperimental"', async () => { - const fileContents = 'Hello I am launch.json, I contain string "pythonExperimental"'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal( - [new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, undefined)], - 'Diagnostics returned are not as expected', - ); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return JustMyCodeDiagnostic if file launch.json contains string "debugStdLib"', async () => { - const fileContents = 'Hello I am launch.json, I contain string "debugStdLib"'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal( - [new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.JustMyCodeDiagnostic, undefined)], - 'Diagnostics returned are not as expected', - ); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return ConfigPythonPathDiagnostic if file launch.json contains string "{config:python.pythonPath}"', async () => { - const fileContents = 'Hello I am launch.json, I contain string {config:python.pythonPath}'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal( - [new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConfigPythonPathDiagnostic, undefined, false)], - 'Diagnostics returned are not as expected', - ); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return ConfigPythonPathDiagnostic if file launch.json contains string "{config:python.interpreterPath}"', async () => { - const fileContents = 'Hello I am launch.json, I contain string {config:python.interpreterPath}'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal( - [new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConfigPythonPathDiagnostic, undefined, false)], - 'Diagnostics returned are not as expected', - ); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('Should return both diagnostics if file launch.json contains string "debugStdLib" and "pythonExperimental"', async () => { - const fileContents = 'Hello I am launch.json, I contain both "debugStdLib" and "pythonExperimental"'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(fileContents)) - .verifiable(TypeMoq.Times.once()); - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal( - [ - new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, undefined), - new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.JustMyCodeDiagnostic, undefined), - ], - 'Diagnostics returned are not as expected', - ); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('All InvalidLaunchJsonDebugger diagnostics with `shouldShowPrompt` set to `true` should display a prompt with 2 buttons where clicking the first button will invoke a command', async () => { - for (const code of [ - DiagnosticCodes.InvalidDebuggerTypeDiagnostic, - DiagnosticCodes.JustMyCodeDiagnostic, - DiagnosticCodes.ConsoleTypeDiagnostic, - ]) { - const diagnostic = TypeMoq.Mock.ofType(); - let options: MessageCommandPrompt | undefined; - diagnostic - .setup((d) => d.code) - .returns(() => code) - .verifiable(TypeMoq.Times.atLeastOnce()); - diagnostic - .setup((d) => d.shouldShowPrompt) - .returns(() => true) - .verifiable(TypeMoq.Times.atLeastOnce()); - messageHandler - .setup((m) => m.handle(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .callback((_, opts: MessageCommandPrompt) => (options = opts)) - .verifiable(TypeMoq.Times.atLeastOnce()); - baseWorkspaceService - .setup((c) => c.getWorkspaceFolder(TypeMoq.It.isAny())) - .returns(() => workspaceFolder) - .verifiable(TypeMoq.Times.atLeastOnce()); - - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - baseWorkspaceService.verifyAll(); - expect(options!.commandPrompts).to.be.lengthOf(2); - expect(options!.commandPrompts[0].prompt).to.be.equal(Diagnostics.yesUpdateLaunch); - expect(options!.commandPrompts[0].command).not.to.be.equal(undefined, 'Command not set'); - } - }); - - test('All InvalidLaunchJsonDebugger diagnostics with `shouldShowPrompt` set to `false` should directly fix launch.json', async () => { - for (const code of [DiagnosticCodes.ConfigPythonPathDiagnostic]) { - let called = false; - (diagnosticService as any).fixLaunchJson = () => { - called = true; - }; - const diagnostic = TypeMoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => code) - .verifiable(TypeMoq.Times.atLeastOnce()); - diagnostic - .setup((d) => d.shouldShowPrompt) - .returns(() => false) - .verifiable(TypeMoq.Times.atLeastOnce()); - messageHandler - .setup((m) => m.handle(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .verifiable(TypeMoq.Times.never()); - baseWorkspaceService - .setup((c) => c.getWorkspaceFolder(TypeMoq.It.isAny())) - .returns(() => workspaceFolder) - .verifiable(TypeMoq.Times.atLeastOnce()); - - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - baseWorkspaceService.verifyAll(); - expect(called).to.equal(true, ''); - } - }); - - test('All InvalidLaunchJsonDebugger diagnostics should display message twice if invoked twice', async () => { - for (const code of [ - DiagnosticCodes.InvalidDebuggerTypeDiagnostic, - DiagnosticCodes.JustMyCodeDiagnostic, - DiagnosticCodes.ConsoleTypeDiagnostic, - ]) { - const diagnostic = TypeMoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => code) - .verifiable(TypeMoq.Times.atLeastOnce()); - diagnostic - .setup((d) => d.invokeHandler) - .returns(() => 'always') - .verifiable(TypeMoq.Times.atLeastOnce()); - messageHandler.reset(); - messageHandler - .setup((m) => m.handle(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .verifiable(TypeMoq.Times.exactly(2)); - baseWorkspaceService - .setup((c) => c.getWorkspaceFolder(TypeMoq.It.isAny())) - .returns(() => workspaceFolder) - .verifiable(TypeMoq.Times.never()); - - await diagnosticService.handle([diagnostic.object]); - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - baseWorkspaceService.verifyAll(); - } - }); - - const codes = [ - DiagnosticCodes.InvalidDebuggerTypeDiagnostic, - DiagnosticCodes.JustMyCodeDiagnostic, - DiagnosticCodes.ConsoleTypeDiagnostic, - ]; - - codes.forEach((code) => { - test('Function fixLaunchJson() returns if there are no workspace folders', async () => { - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => undefined) - .verifiable(TypeMoq.Times.atLeastOnce()); - await (diagnosticService as any).fixLaunchJson(code); - workspaceService.verifyAll(); - }); - - test('Function fixLaunchJson() returns if file launch.json does not exist', async () => { - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve('')) - .verifiable(TypeMoq.Times.never()); - await (diagnosticService as any).fixLaunchJson(code); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - }); - - test('File launch.json is fixed correctly when code equals JustMyCodeDiagnostic', async () => { - const launchJson = '{"debugStdLib": true, "debugStdLib": false}'; - const correctedlaunchJson = '{"justMyCode": false, "justMyCode": true}'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(launchJson)) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.writeFile(TypeMoq.It.isAnyString(), correctedlaunchJson)) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.once()); - await (diagnosticService as any).fixLaunchJson(DiagnosticCodes.JustMyCodeDiagnostic); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('File launch.json is fixed correctly when code equals InvalidDebuggerTypeDiagnostic', async () => { - const launchJson = '{"Python Experimental: task" "pythonExperimental"}'; - const correctedlaunchJson = '{"Python: task" "python"}'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(launchJson)) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.writeFile(TypeMoq.It.isAnyString(), correctedlaunchJson)) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.once()); - await (diagnosticService as any).fixLaunchJson(DiagnosticCodes.InvalidDebuggerTypeDiagnostic); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('File launch.json is fixed correctly when code equals ConsoleTypeDiagnostic', async () => { - const launchJson = '{"console": "none"}'; - const correctedlaunchJson = '{"console": "internalConsole"}'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(launchJson)) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.writeFile(TypeMoq.It.isAnyString(), correctedlaunchJson)) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.once()); - await (diagnosticService as any).fixLaunchJson(DiagnosticCodes.ConsoleTypeDiagnostic); - workspaceService.verifyAll(); - fs.verifyAll(); - }); - - test('File launch.json is fixed correctly when code equals ConfigPythonPathDiagnostic', async () => { - const launchJson = '"pythonPath": "{config:python.pythonPath}{config:python.interpreterPath}"'; - const correctedlaunchJson = '"python": "{command:python.interpreterPath}{command:python.interpreterPath}"'; - workspaceService - .setup((w) => w.workspaceFolders) - .returns(() => [workspaceFolder]) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.fileExists(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - fs.setup((w) => w.readFile(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(launchJson)) - .verifiable(TypeMoq.Times.atLeastOnce()); - fs.setup((w) => w.writeFile(TypeMoq.It.isAnyString(), correctedlaunchJson)) - .returns(() => Promise.resolve()) - .verifiable(TypeMoq.Times.once()); - await (diagnosticService as any).fixLaunchJson(DiagnosticCodes.ConfigPythonPathDiagnostic); - workspaceService.verifyAll(); - fs.verifyAll(); - }); -}); diff --git a/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts b/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts deleted file mode 100644 index 8c835003ffef..000000000000 --- a/src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import * as typemoq from 'typemoq'; -import { Uri } from 'vscode'; -import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; -import { InvalidPythonPathInDebuggerService } from '../../../../client/application/diagnostics/checks/invalidPythonPathInDebugger'; -import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; -import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; -import { - DiagnosticCommandPromptHandlerServiceId, - MessageCommandPrompt, -} from '../../../../client/application/diagnostics/promptHandler'; -import { - IDiagnostic, - IDiagnosticCommand, - IDiagnosticHandlerService, - IInvalidPythonPathInDebuggerService, -} from '../../../../client/application/diagnostics/types'; -import { CommandsWithoutArgs } from '../../../../client/common/application/commands'; -import { IDocumentManager, IWorkspaceService } from '../../../../client/common/application/types'; -import { IConfigurationService, IPythonSettings } from '../../../../client/common/types'; -import { PythonPathSource } from '../../../../client/debugger/extension/types'; -import { IInterpreterHelper } from '../../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../../client/ioc/types'; - -suite('Application Diagnostics - Checks Python Path in debugger', () => { - let diagnosticService: IInvalidPythonPathInDebuggerService; - let messageHandler: typemoq.IMock>; - let commandFactory: typemoq.IMock; - let configService: typemoq.IMock; - let helper: typemoq.IMock; - let workspaceService: typemoq.IMock; - let docMgr: typemoq.IMock; - setup(() => { - const serviceContainer = typemoq.Mock.ofType(); - messageHandler = typemoq.Mock.ofType>(); - serviceContainer - .setup((s) => - s.get( - typemoq.It.isValue(IDiagnosticHandlerService), - typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId), - ), - ) - .returns(() => messageHandler.object); - commandFactory = typemoq.Mock.ofType(); - docMgr = typemoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) - .returns(() => commandFactory.object); - configService = typemoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(typemoq.It.isValue(IConfigurationService))) - .returns(() => configService.object); - helper = typemoq.Mock.ofType(); - serviceContainer.setup((s) => s.get(typemoq.It.isValue(IInterpreterHelper))).returns(() => helper.object); - workspaceService = typemoq.Mock.ofType(); - serviceContainer - .setup((s) => s.get(typemoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - - diagnosticService = new (class extends InvalidPythonPathInDebuggerService { - public _clear() { - while (BaseDiagnosticsService.handledDiagnosticCodeKeys.length > 0) { - BaseDiagnosticsService.handledDiagnosticCodeKeys.shift(); - } - } - })( - serviceContainer.object, - workspaceService.object, - commandFactory.object, - helper.object, - docMgr.object, - configService.object, - [], - messageHandler.object, - ); - (diagnosticService as any)._clear(); - }); - - test('Can handle InvalidPythonPathInDebugger diagnostics', async () => { - for (const code of [ - DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, - DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic, - ]) { - const diagnostic = typemoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => code) - .verifiable(typemoq.Times.atLeastOnce()); - - const canHandle = await diagnosticService.canHandle(diagnostic.object); - expect(canHandle).to.be.equal(true, `Should be able to handle ${code}`); - diagnostic.verifyAll(); - } - }); - test('Can not handle non-InvalidPythonPathInDebugger diagnostics', async () => { - const diagnostic = typemoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => 'Something Else' as any) - .verifiable(typemoq.Times.atLeastOnce()); - - const canHandle = await diagnosticService.canHandle(diagnostic.object); - expect(canHandle).to.be.equal(false, 'Invalid value'); - diagnostic.verifyAll(); - }); - test('Should return empty diagnostics', async () => { - const diagnostics = await diagnosticService.diagnose(undefined); - expect(diagnostics).to.be.deep.equal([]); - }); - test('InvalidPythonPathInDebuggerSettings diagnostic should display one option to with a command', async () => { - const diagnostic = typemoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic) - .verifiable(typemoq.Times.atLeastOnce()); - const interpreterSelectionCommand = typemoq.Mock.ofType(); - commandFactory - .setup((f) => - f.createCommand( - typemoq.It.isAny(), - typemoq.It.isObjectWith>({ - type: 'executeVSCCommand', - }), - ), - ) - .returns(() => interpreterSelectionCommand.object) - .verifiable(typemoq.Times.once()); - messageHandler.setup((m) => m.handle(typemoq.It.isAny(), typemoq.It.isAny())).verifiable(typemoq.Times.once()); - - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - }); - test('InvalidPythonPathInDebuggerSettings diagnostic should display message once if invoked twice', async () => { - const diagnostic = typemoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic) - .verifiable(typemoq.Times.atLeastOnce()); - diagnostic - .setup((d) => d.invokeHandler) - .returns(() => 'default') - .verifiable(typemoq.Times.atLeastOnce()); - const interpreterSelectionCommand = typemoq.Mock.ofType(); - commandFactory - .setup((f) => - f.createCommand( - typemoq.It.isAny(), - typemoq.It.isObjectWith>({ - type: 'executeVSCCommand', - }), - ), - ) - .returns(() => interpreterSelectionCommand.object) - .verifiable(typemoq.Times.exactly(1)); - messageHandler - .setup((m) => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .verifiable(typemoq.Times.exactly(1)); - - await diagnosticService.handle([diagnostic.object]); - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - }); - test('InvalidPythonPathInDebuggerSettings diagnostic should display message twice if invoked twice', async () => { - const diagnostic = typemoq.Mock.ofType(); - diagnostic - .setup((d) => d.code) - .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic) - .verifiable(typemoq.Times.atLeastOnce()); - diagnostic - .setup((d) => d.invokeHandler) - .returns(() => 'always') - .verifiable(typemoq.Times.atLeastOnce()); - const interpreterSelectionCommand = typemoq.Mock.ofType(); - commandFactory - .setup((f) => - f.createCommand( - typemoq.It.isAny(), - typemoq.It.isObjectWith>({ - type: 'executeVSCCommand', - }), - ), - ) - .returns(() => interpreterSelectionCommand.object) - .verifiable(typemoq.Times.exactly(2)); - messageHandler - .setup((m) => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .verifiable(typemoq.Times.exactly(2)); - - await diagnosticService.handle([diagnostic.object]); - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - }); - test('InvalidPythonPathInDebuggerLaunch diagnostic should display one option to with a command', async () => { - const diagnostic = typemoq.Mock.ofType(); - let options: MessageCommandPrompt | undefined; - diagnostic - .setup((d) => d.code) - .returns(() => DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic) - .verifiable(typemoq.Times.atLeastOnce()); - messageHandler - .setup((m) => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .callback((_, opts: MessageCommandPrompt) => (options = opts)) - .verifiable(typemoq.Times.once()); - - await diagnosticService.handle([diagnostic.object]); - - diagnostic.verifyAll(); - commandFactory.verifyAll(); - messageHandler.verifyAll(); - expect(options!.commandPrompts).to.be.lengthOf(1); - expect(options!.commandPrompts[0].prompt).to.be.equal('Open launch.json'); - }); - test('Ensure we get python path from config when path = ${command:python.interpreterPath}', async () => { - const pythonPath = '${command:python.interpreterPath}'; - - const settings = typemoq.Mock.ofType(); - settings - .setup((s) => s.pythonPath) - .returns(() => 'p') - .verifiable(typemoq.Times.once()); - configService - .setup((c) => c.getSettings(typemoq.It.isAny())) - .returns(() => settings.object) - .verifiable(typemoq.Times.once()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue('p'))) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath); - - settings.verifyAll(); - configService.verifyAll(); - helper.verifyAll(); - expect(valid).to.be.equal(true, 'not valid'); - }); - test('Ensure ${workspaceFolder} is not expanded when a resource is not passed', async () => { - const pythonPath = '${workspaceFolder}/venv/bin/python'; - - workspaceService - .setup((c) => c.getWorkspaceFolder(typemoq.It.isAny())) - .returns(() => undefined) - .verifiable(typemoq.Times.never()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isAny())) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - await diagnosticService.validatePythonPath(pythonPath); - - configService.verifyAll(); - helper.verifyAll(); - }); - test('Ensure ${workspaceFolder} is expanded', async () => { - const pythonPath = '${workspaceFolder}/venv/bin/python'; - - const workspaceFolder = { uri: Uri.parse('full/path/to/workspace'), name: '', index: 0 }; - const expectedPath = `${workspaceFolder.uri.fsPath}/venv/bin/python`; - - workspaceService - .setup((c) => c.getWorkspaceFolder(typemoq.It.isAny())) - .returns(() => workspaceFolder) - .verifiable(typemoq.Times.once()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue(expectedPath))) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath( - pythonPath, - PythonPathSource.settingsJson, - Uri.parse('something'), - ); - - configService.verifyAll(); - helper.verifyAll(); - expect(valid).to.be.equal(true, 'not valid'); - }); - test('Ensure ${env:XYZ123} is expanded', async () => { - const pythonPath = '${env:XYZ123}/venv/bin/python'; - - process.env.XYZ123 = 'something/else'; - const expectedPath = `${process.env.XYZ123}/venv/bin/python`; - workspaceService - .setup((c) => c.getWorkspaceFolder(typemoq.It.isAny())) - .returns(() => undefined) - .verifiable(typemoq.Times.once()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue(expectedPath))) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath); - - configService.verifyAll(); - helper.verifyAll(); - expect(valid).to.be.equal(true, 'not valid'); - }); - test('Ensure we get python path from config when path = undefined', async () => { - const pythonPath = undefined; - - const settings = typemoq.Mock.ofType(); - settings - .setup((s) => s.pythonPath) - .returns(() => 'p') - .verifiable(typemoq.Times.once()); - configService - .setup((c) => c.getSettings(typemoq.It.isAny())) - .returns(() => settings.object) - .verifiable(typemoq.Times.once()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue('p'))) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath); - - settings.verifyAll(); - configService.verifyAll(); - helper.verifyAll(); - expect(valid).to.be.equal(true, 'not valid'); - }); - test('Ensure we do not get python path from config when path is provided', async () => { - const pythonPath = path.join('a', 'b'); - - const settings = typemoq.Mock.ofType(); - configService - .setup((c) => c.getSettings(typemoq.It.isAny())) - .returns(() => settings.object) - .verifiable(typemoq.Times.never()); - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve({})) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath); - - configService.verifyAll(); - helper.verifyAll(); - expect(valid).to.be.equal(true, 'not valid'); - }); - test('Ensure InvalidPythonPathInDebuggerLaunch diagnostic is handled when path is invalid in launch.json', async () => { - const pythonPath = path.join('a', 'b'); - const settings = typemoq.Mock.ofType(); - configService - .setup((c) => c.getSettings(typemoq.It.isAny())) - .returns(() => settings.object) - .verifiable(typemoq.Times.never()); - let handleInvoked = false; - diagnosticService.handle = (diagnostics) => { - if ( - diagnostics.length !== 0 && - diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic - ) { - handleInvoked = true; - } - return Promise.resolve(); - }; - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue(pythonPath))) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath, PythonPathSource.launchJson); - - helper.verifyAll(); - expect(valid).to.be.equal(false, 'should be invalid'); - expect(handleInvoked).to.be.equal(true, 'should be invoked'); - }); - test('Ensure InvalidPythonPathInDebuggerSettings diagnostic is handled when path is invalid in settings.json', async () => { - const pythonPath = undefined; - const settings = typemoq.Mock.ofType(); - settings - .setup((s) => s.pythonPath) - .returns(() => 'p') - .verifiable(typemoq.Times.once()); - configService - .setup((c) => c.getSettings(typemoq.It.isAny())) - .returns(() => settings.object) - .verifiable(typemoq.Times.once()); - let handleInvoked = false; - diagnosticService.handle = (diagnostics) => { - if ( - diagnostics.length !== 0 && - diagnostics[0].code === DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic - ) { - handleInvoked = true; - } - return Promise.resolve(); - }; - helper - .setup((h) => h.getInterpreterInformation(typemoq.It.isValue('p'))) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.once()); - - const valid = await diagnosticService.validatePythonPath(pythonPath, PythonPathSource.settingsJson); - - helper.verifyAll(); - expect(valid).to.be.equal(false, 'should be invalid'); - expect(handleInvoked).to.be.equal(true, 'should be invoked'); - }); -}); diff --git a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts index 2397743274c1..2eecf052e433 100644 --- a/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts +++ b/src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts @@ -7,7 +7,6 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { EventEmitter, Uri } from 'vscode'; import { BaseDiagnosticsService } from '../../../../client/application/diagnostics/base'; -import { InvalidLaunchJsonDebuggerDiagnostic } from '../../../../client/application/diagnostics/checks/invalidLaunchJsonDebugger'; import { DefaultShellDiagnostic, InvalidPythonInterpreterDiagnostic, @@ -586,39 +585,6 @@ suite('Application Diagnostics - Checks Python Interpreter', () => { await diagnosticServiceMock.object.handle([diagnostic]); - messageHandler.verifyAll(); - commandFactory.verifyAll(); - }); - test('Getting command prompts for an unsupported diagnostic code should throw an error', async () => { - const diagnostic = new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.JustMyCodeDiagnostic, undefined); - const cmd = ({} as any) as IDiagnosticCommand; - - messageHandler - .setup((i) => i.handle(typemoq.It.isAny(), typemoq.It.isAny())) - .callback((_d, p: MessageCommandPrompt) => p) - .returns(() => Promise.resolve()) - .verifiable(typemoq.Times.never()); - commandFactory - .setup((f) => - f.createCommand( - typemoq.It.isAny(), - typemoq.It.isObjectWith>({ - type: 'executeVSCCommand', - }), - ), - ) - .returns(() => cmd) - .verifiable(typemoq.Times.never()); - - try { - await diagnosticService.handle([diagnostic]); - } catch (err) { - expect((err as Error).message).to.be.equal( - "Invalid diagnostic for 'InvalidPythonInterpreterService'", - 'Error message is different', - ); - } - messageHandler.verifyAll(); commandFactory.verifyAll(); }); diff --git a/src/test/debugger/common/constants.ts b/src/test/debugger/common/constants.ts deleted file mode 100644 index a9bcc64f1a24..000000000000 --- a/src/test/debugger/common/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// Sometimes PTVSD can take a while for thread & other events to be reported. -export const DEBUGGER_TIMEOUT = 20000; diff --git a/src/test/debugger/common/protocolparser.test.ts b/src/test/debugger/common/protocolparser.test.ts deleted file mode 100644 index 117a58a7bc66..000000000000 --- a/src/test/debugger/common/protocolparser.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { expect } from 'chai'; -import { PassThrough } from 'stream'; -import { createDeferred } from '../../../client/common/utils/async'; -import { ProtocolParser } from '../../../client/debugger/extension/helpers/protocolParser'; -import { sleep } from '../../common'; - -suite('Debugging - Protocol Parser', () => { - test('Test request, response and event messages', async () => { - const stream = new PassThrough(); - - const protocolParser = new ProtocolParser(); - protocolParser.connect(stream); - let messagesDetected = 0; - protocolParser.on('data', () => (messagesDetected += 1)); - const requestDetected = new Promise((resolve) => { - protocolParser.on('request_initialize', () => resolve(true)); - }); - const responseDetected = new Promise((resolve) => { - protocolParser.on('response_initialize', () => resolve(true)); - }); - const eventDetected = new Promise((resolve) => { - protocolParser.on('event_initialized', () => resolve(true)); - }); - - stream.write( - 'Content-Length: 289\r\n\r\n{"command":"initialize","arguments":{"clientID":"vscode","adapterID":"pythonExperiment","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,"supportsVariableType":true,"supportsVariablePaging":true,"supportsRunInTerminalRequest":true,"locale":"en-us"},"type":"request","seq":1}', - ); - await expect(requestDetected).to.eventually.equal(true, 'request not parsed'); - - stream.write( - 'Content-Length: 265\r\n\r\n{"seq":1,"type":"response","request_seq":1,"command":"initialize","success":true,"body":{"supportsEvaluateForHovers":false,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsFunctionBreakpoints":false,"supportsSetVariable":true}}', - ); - await expect(responseDetected).to.eventually.equal(true, 'response not parsed'); - - stream.write('Content-Length: 63\r\n\r\n{"type": "event", "seq": 1, "event": "initialized", "body": {}}'); - await expect(eventDetected).to.eventually.equal(true, 'event not parsed'); - - expect(messagesDetected).to.be.equal(3, 'incorrect number of protocol messages'); - }); - test('Ensure messages are not received after disposing the parser', async () => { - const stream = new PassThrough(); - - const protocolParser = new ProtocolParser(); - protocolParser.connect(stream); - let messagesDetected = 0; - protocolParser.on('data', () => (messagesDetected += 1)); - const requestDetected = new Promise((resolve) => { - protocolParser.on('request_initialize', () => resolve(true)); - }); - stream.write( - 'Content-Length: 289\r\n\r\n{"command":"initialize","arguments":{"clientID":"vscode","adapterID":"pythonExperiment","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,"supportsVariableType":true,"supportsVariablePaging":true,"supportsRunInTerminalRequest":true,"locale":"en-us"},"type":"request","seq":1}', - ); - await expect(requestDetected).to.eventually.equal(true, 'request not parsed'); - - protocolParser.dispose(); - - const responseDetected = createDeferred(); - protocolParser.on('response_initialize', () => responseDetected.resolve(true)); - - stream.write( - 'Content-Length: 265\r\n\r\n{"seq":1,"type":"response","request_seq":1,"command":"initialize","success":true,"body":{"supportsEvaluateForHovers":false,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsFunctionBreakpoints":false,"supportsSetVariable":true}}', - ); - // Wait for messages to go through and get parsed (unnecenssary, but add for testing edge cases). - await sleep(1000); - expect(responseDetected.completed).to.be.equal(false, 'Promise should not have resolved'); - }); -}); diff --git a/src/test/debugger/extension/adapter/activator.unit.test.ts b/src/test/debugger/extension/adapter/activator.unit.test.ts deleted file mode 100644 index e8c6ef74fc2a..000000000000 --- a/src/test/debugger/extension/adapter/activator.unit.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; -import { IExtensionSingleActivationService } from '../../../../client/activation/types'; -import { CommandManager } from '../../../../client/common/application/commandManager'; -import { DebugService } from '../../../../client/common/application/debugService'; -import { ICommandManager, IDebugService } from '../../../../client/common/application/types'; -import { ConfigurationService } from '../../../../client/common/configuration/service'; -import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../../../client/common/types'; -import { DebugAdapterActivator } from '../../../../client/debugger/extension/adapter/activator'; -import { DebugAdapterDescriptorFactory } from '../../../../client/debugger/extension/adapter/factory'; -import { DebugSessionLoggingFactory } from '../../../../client/debugger/extension/adapter/logging'; -import { OutdatedDebuggerPromptFactory } from '../../../../client/debugger/extension/adapter/outdatedDebuggerPrompt'; -import { AttachProcessProviderFactory } from '../../../../client/debugger/extension/attachQuickPick/factory'; -import { IAttachProcessProviderFactory } from '../../../../client/debugger/extension/attachQuickPick/types'; -import { - IDebugAdapterDescriptorFactory, - IDebugSessionLoggingFactory, - IOutdatedDebuggerPromptFactory, -} from '../../../../client/debugger/extension/types'; -import { clearTelemetryReporter } from '../../../../client/telemetry'; -import { noop } from '../../../core'; - -suite('Debugging - Adapter Factory and logger Registration', () => { - let activator: IExtensionSingleActivationService; - let debugService: IDebugService; - let commandManager: ICommandManager; - let descriptorFactory: IDebugAdapterDescriptorFactory; - let loggingFactory: IDebugSessionLoggingFactory; - let debuggerPromptFactory: IOutdatedDebuggerPromptFactory; - let disposableRegistry: IDisposableRegistry; - let attachFactory: IAttachProcessProviderFactory; - let configService: IConfigurationService; - - setup(() => { - attachFactory = mock(AttachProcessProviderFactory); - - debugService = mock(DebugService); - when(debugService.onDidStartDebugSession).thenReturn(() => noop as any); - - commandManager = mock(CommandManager); - - configService = mock(ConfigurationService); - when(configService.getSettings(undefined)).thenReturn(({ - experiments: { enabled: true }, - } as any) as IPythonSettings); - - descriptorFactory = mock(DebugAdapterDescriptorFactory); - loggingFactory = mock(DebugSessionLoggingFactory); - debuggerPromptFactory = mock(OutdatedDebuggerPromptFactory); - disposableRegistry = []; - - activator = new DebugAdapterActivator( - instance(debugService), - instance(configService), - instance(commandManager), - instance(descriptorFactory), - instance(loggingFactory), - instance(debuggerPromptFactory), - disposableRegistry, - instance(attachFactory), - ); - }); - - teardown(() => { - clearTelemetryReporter(); - }); - - test('Register Debug adapter factory', async () => { - await activator.activate(); - - verify(debugService.registerDebugAdapterTrackerFactory('python', instance(loggingFactory))).once(); - verify(debugService.registerDebugAdapterTrackerFactory('python', instance(debuggerPromptFactory))).once(); - verify(debugService.registerDebugAdapterDescriptorFactory('python', instance(descriptorFactory))).once(); - }); - - test('Register a disposable item', async () => { - const disposable = { dispose: noop }; - when(debugService.registerDebugAdapterTrackerFactory(anything(), anything())).thenReturn(disposable); - when(debugService.registerDebugAdapterDescriptorFactory(anything(), anything())).thenReturn(disposable); - when(debugService.onDidStartDebugSession).thenReturn(() => disposable); - - await activator.activate(); - - assert.deepEqual(disposableRegistry, [disposable, disposable, disposable, disposable]); - }); -}); diff --git a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts b/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts deleted file mode 100644 index 7c7977ab8480..000000000000 --- a/src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as typemoq from 'typemoq'; -import { DebugConfiguration, Uri } from 'vscode'; -import { IMultiStepInputFactory, MultiStepInput } from '../../../../client/common/utils/multiStepInput'; -import { PythonDebugConfigurationService } from '../../../../client/debugger/extension/configuration/debugConfigurationService'; -import { IDebugConfigurationResolver } from '../../../../client/debugger/extension/configuration/types'; -import { DebugConfigurationState } from '../../../../client/debugger/extension/types'; -import { AttachRequestArguments, LaunchRequestArguments } from '../../../../client/debugger/types'; - -suite('Debugging - Configuration Service', () => { - let attachResolver: typemoq.IMock>; - let launchResolver: typemoq.IMock>; - let configService: TestPythonDebugConfigurationService; - let multiStepFactory: typemoq.IMock; - - class TestPythonDebugConfigurationService extends PythonDebugConfigurationService { - public static async pickDebugConfiguration( - input: MultiStepInput, - state: DebugConfigurationState, - ) { - return PythonDebugConfigurationService.pickDebugConfiguration(input, state); - } - } - setup(() => { - attachResolver = typemoq.Mock.ofType>(); - launchResolver = typemoq.Mock.ofType>(); - multiStepFactory = typemoq.Mock.ofType(); - - configService = new TestPythonDebugConfigurationService( - attachResolver.object, - launchResolver.object, - multiStepFactory.object, - ); - }); - test('Should use attach resolver when passing attach config', async () => { - const config = ({ - request: 'attach', - } as DebugConfiguration) as AttachRequestArguments; - const folder = { name: '1', index: 0, uri: Uri.parse('1234') }; - const expectedConfig = { yay: 1 }; - - attachResolver - .setup((a) => - a.resolveDebugConfiguration(typemoq.It.isValue(folder), typemoq.It.isValue(config), typemoq.It.isAny()), - ) - .returns(() => Promise.resolve((expectedConfig as unknown) as AttachRequestArguments)) - .verifiable(typemoq.Times.once()); - launchResolver - .setup((a) => a.resolveDebugConfiguration(typemoq.It.isAny(), typemoq.It.isAny(), typemoq.It.isAny())) - .verifiable(typemoq.Times.never()); - - const resolvedConfig = await configService.resolveDebugConfiguration(folder, config as DebugConfiguration); - - expect(resolvedConfig).to.deep.equal(expectedConfig); - attachResolver.verifyAll(); - launchResolver.verifyAll(); - }); - [{ request: 'launch' }, { request: undefined }].forEach((config) => { - test(`Should use launch resolver when passing launch config with request=${config.request}`, async () => { - const folder = { name: '1', index: 0, uri: Uri.parse('1234') }; - const expectedConfig = { yay: 1 }; - - launchResolver - .setup((a) => - a.resolveDebugConfiguration( - typemoq.It.isValue(folder), - typemoq.It.isValue((config as DebugConfiguration) as LaunchRequestArguments), - typemoq.It.isAny(), - ), - ) - .returns(() => Promise.resolve((expectedConfig as unknown) as LaunchRequestArguments)) - .verifiable(typemoq.Times.once()); - attachResolver - .setup((a) => a.resolveDebugConfiguration(typemoq.It.isAny(), typemoq.It.isAny(), typemoq.It.isAny())) - .verifiable(typemoq.Times.never()); - - const resolvedConfig = await configService.resolveDebugConfiguration(folder, config as DebugConfiguration); - - expect(resolvedConfig).to.deep.equal(expectedConfig); - attachResolver.verifyAll(); - launchResolver.verifyAll(); - }); - }); - test('Picker should be displayed', async () => { - const state = ({ configs: [], folder: {}, token: undefined } as unknown) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); - multiStepInput - .setup((i) => i.showQuickPick(typemoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.once()); - - await TestPythonDebugConfigurationService.pickDebugConfiguration(multiStepInput.object, state); - - multiStepInput.verifyAll(); - }); - test('Existing Configuration items must be removed before displaying picker', async () => { - const state = ({ configs: [1, 2, 3], folder: {}, token: undefined } as unknown) as DebugConfigurationState; - const multiStepInput = typemoq.Mock.ofType>(); - multiStepInput - .setup((i) => i.showQuickPick(typemoq.It.isAny())) - .returns(() => Promise.resolve(undefined)) - .verifiable(typemoq.Times.once()); - - await TestPythonDebugConfigurationService.pickDebugConfiguration(multiStepInput.object, state); - - multiStepInput.verifyAll(); - expect(Object.keys(state.config)).to.be.lengthOf(0); - }); - test('Ensure generated config is returned', async () => { - const expectedConfig = { yes: 'Updated' }; - const multiStepInput = { - run: (_: unknown, state: DebugConfiguration) => { - Object.assign(state.config, expectedConfig); - return Promise.resolve(); - }, - }; - multiStepFactory - .setup((f) => f.create()) - .returns(() => multiStepInput as MultiStepInput) - .verifiable(typemoq.Times.once()); - TestPythonDebugConfigurationService.pickDebugConfiguration = (_, state) => { - Object.assign(state.config, expectedConfig); - return Promise.resolve(); - }; - const config = await configService.provideDebugConfigurations!(({} as unknown) as undefined); - - multiStepFactory.verifyAll(); - expect(config).to.deep.equal([expectedConfig]); - }); - test('Ensure `undefined` is returned if QuickPick is cancelled', async () => { - const multiStepInput = { - run: (_: unknown, _state: DebugConfiguration) => Promise.resolve(), - }; - const folder = { name: '1', index: 0, uri: Uri.parse('1234') }; - multiStepFactory - .setup((f) => f.create()) - .returns(() => multiStepInput as MultiStepInput) - .verifiable(typemoq.Times.once()); - const config = await configService.resolveDebugConfiguration(folder, {} as DebugConfiguration); - - multiStepFactory.verifyAll(); - - expect(config).to.equal(undefined, `Config should be undefined`); - }); - test('Use cached debug configuration', async () => { - const folder = { name: '1', index: 0, uri: Uri.parse('1234') }; - const expectedConfig = { - name: 'File', - type: 'python', - request: 'launch', - program: '${file}', - console: 'integratedTerminal', - }; - const multiStepInput = { - run: (_: unknown, state: DebugConfiguration) => { - Object.assign(state.config, expectedConfig); - return Promise.resolve(); - }, - }; - multiStepFactory - .setup((f) => f.create()) - .returns(() => multiStepInput as MultiStepInput) - .verifiable(typemoq.Times.once()); // this should be called only once. - - launchResolver - .setup((a) => a.resolveDebugConfiguration(typemoq.It.isAny(), typemoq.It.isAny(), typemoq.It.isAny())) - .returns(() => Promise.resolve(expectedConfig as LaunchRequestArguments)) - .verifiable(typemoq.Times.exactly(2)); // this should be called twice with the same config. - - await configService.resolveDebugConfiguration(folder, {} as DebugConfiguration); - await configService.resolveDebugConfiguration(folder, {} as DebugConfiguration); - - multiStepFactory.verifyAll(); - launchResolver.verifyAll(); - }); -}); diff --git a/src/test/debugger/extension/configuration/launch.json/completionProvider.unit.test.ts b/src/test/debugger/extension/configuration/launch.json/completionProvider.unit.test.ts deleted file mode 100644 index a850d50150ae..000000000000 --- a/src/test/debugger/extension/configuration/launch.json/completionProvider.unit.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { deepEqual, instance, mock, verify } from 'ts-mockito'; -import * as typemoq from 'typemoq'; -import { - CancellationTokenSource, - CompletionItem, - CompletionItemKind, - Position, - SnippetString, - TextDocument, - Uri, -} from 'vscode'; -import { LanguageService } from '../../../../../client/common/application/languageService'; -import { ILanguageService } from '../../../../../client/common/application/types'; -import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; -import { LaunchJsonCompletionProvider } from '../../../../../client/debugger/extension/configuration/launch.json/completionProvider'; - -suite('Debugging - launch.json Completion Provider', () => { - let completionProvider: LaunchJsonCompletionProvider; - let languageService: ILanguageService; - - setup(() => { - languageService = mock(LanguageService); - completionProvider = new LaunchJsonCompletionProvider(instance(languageService), []); - }); - test('Activation will register the completion provider', async () => { - await completionProvider.activate(); - verify( - languageService.registerCompletionItemProvider(deepEqual({ language: 'json' }), completionProvider), - ).once(); - verify( - languageService.registerCompletionItemProvider(deepEqual({ language: 'jsonc' }), completionProvider), - ).once(); - }); - test('Cannot provide completions for non launch.json files', () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - document.setup((doc) => doc.uri).returns(() => Uri.file(__filename)); - assert.strictEqual(LaunchJsonCompletionProvider.canProvideCompletions(document.object, position), false); - - document.reset(); - document.setup((doc) => doc.uri).returns(() => Uri.file('settings.json')); - assert.strictEqual(LaunchJsonCompletionProvider.canProvideCompletions(document.object, position), false); - }); - function testCanProvideCompletions(position: Position, offset: number, json: string, expectedValue: boolean) { - const document = typemoq.Mock.ofType(); - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.uri).returns(() => Uri.file('launch.json')); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => offset); - const canProvideCompletions = LaunchJsonCompletionProvider.canProvideCompletions(document.object, position); - assert.strictEqual(canProvideCompletions, expectedValue); - } - test('Cannot provide completions when there is no configurations section in json', () => { - const position = new Position(0, 0); - const config = `{ - "version": "0.1.0" -}`; - testCanProvideCompletions(position, 1, config as string, false); - }); - test('Cannot provide completions when cursor position is not in configurations array', () => { - const position = new Position(0, 0); - const json = `{ - "version": "0.1.0", - "configurations": [] -}`; - testCanProvideCompletions(position, 10, json, false); - }); - test('Cannot provide completions when cursor position is in an empty configurations array', () => { - const position = new Position(0, 0); - const json = `{ - "version": "0.1.0", - "configurations": [ - # Cursor Position - ] -}`; - testCanProvideCompletions(position, json.indexOf('# Cursor Position'), json, true); - }); - test('No Completions for non launch.json', async () => { - const document = typemoq.Mock.ofType(); - document.setup((doc) => doc.uri).returns(() => Uri.file('settings.json')); - const { token } = new CancellationTokenSource(); - const position = new Position(0, 0); - - const completions = await completionProvider.provideCompletionItems(document.object, position, token); - - assert.strictEqual(completions.length, 0); - }); - test('No Completions for files ending with launch.json', async () => { - const document = typemoq.Mock.ofType(); - document.setup((doc) => doc.uri).returns(() => Uri.file('x-launch.json')); - const { token } = new CancellationTokenSource(); - const position = new Position(0, 0); - - const completions = await completionProvider.provideCompletionItems(document.object, position, token); - - assert.strictEqual(completions.length, 0); - }); - test('Get Completions', async () => { - const json = `{ - "version": "0.1.0", - "configurations": [ - # Cursor Position - ] -}`; - - const document = typemoq.Mock.ofType(); - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.uri).returns(() => Uri.file('launch.json')); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.indexOf('# Cursor Position')); - const position = new Position(0, 0); - const { token } = new CancellationTokenSource(); - - const completions = await completionProvider.provideCompletionItems(document.object, position, token); - - assert.strictEqual(completions.length, 1); - - const expectedCompletionItem: CompletionItem = { - command: { - command: 'python.SelectAndInsertDebugConfiguration', - title: DebugConfigStrings.launchJsonCompletions.description, - arguments: [document.object, position, token], - }, - documentation: DebugConfigStrings.launchJsonCompletions.description, - sortText: 'AAAA', - preselect: true, - kind: CompletionItemKind.Enum, - label: DebugConfigStrings.launchJsonCompletions.label, - insertText: new SnippetString(), - }; - - assert.deepEqual(completions[0], expectedCompletionItem); - }); -}); diff --git a/src/test/debugger/extension/configuration/launch.json/updaterServer.unit.test.ts b/src/test/debugger/extension/configuration/launch.json/updaterServer.unit.test.ts deleted file mode 100644 index b2addd24267b..000000000000 --- a/src/test/debugger/extension/configuration/launch.json/updaterServer.unit.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { instance, mock, verify } from 'ts-mockito'; -import { CommandManager } from '../../../../../client/common/application/commandManager'; -import { ICommandManager } from '../../../../../client/common/application/types'; -import { PythonDebugConfigurationService } from '../../../../../client/debugger/extension/configuration/debugConfigurationService'; -import { LaunchJsonUpdaterService } from '../../../../../client/debugger/extension/configuration/launch.json/updaterService'; -import { LaunchJsonUpdaterServiceHelper } from '../../../../../client/debugger/extension/configuration/launch.json/updaterServiceHelper'; -import { IDebugConfigurationService } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - launch.json Updater Service', () => { - let helper: LaunchJsonUpdaterServiceHelper; - let commandManager: ICommandManager; - let debugConfigService: IDebugConfigurationService; - setup(() => { - commandManager = mock(CommandManager); - debugConfigService = mock(PythonDebugConfigurationService); - helper = new LaunchJsonUpdaterServiceHelper(instance(debugConfigService)); - }); - test('Activation will register the required commands', async () => { - const service = new LaunchJsonUpdaterService([], instance(debugConfigService)); - await service.activate(); - verify( - commandManager.registerCommand( - 'python.SelectAndInsertDebugConfiguration', - helper.selectAndInsertDebugConfig, - helper, - ), - ); - }); -}); diff --git a/src/test/debugger/extension/configuration/launch.json/updaterServerHelper.unit.test.ts b/src/test/debugger/extension/configuration/launch.json/updaterServerHelper.unit.test.ts deleted file mode 100644 index 53118d68025e..000000000000 --- a/src/test/debugger/extension/configuration/launch.json/updaterServerHelper.unit.test.ts +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { instance, mock, when } from 'ts-mockito'; -import * as typemoq from 'typemoq'; -import { - CancellationTokenSource, - DebugConfiguration, - Position, - Range, - TextDocument, - TextEditor, - TextLine, - Uri, -} from 'vscode'; -import { PythonDebugConfigurationService } from '../../../../../client/debugger/extension/configuration/debugConfigurationService'; -import { LaunchJsonUpdaterServiceHelper } from '../../../../../client/debugger/extension/configuration/launch.json/updaterServiceHelper'; -import { IDebugConfigurationService } from '../../../../../client/debugger/extension/types'; -import * as windowApis from '../../../../../client/common/vscodeApis/windowApis'; -import * as workspaceApis from '../../../../../client/common/vscodeApis/workspaceApis'; -import * as commandApis from '../../../../../client/common/vscodeApis/commandApis'; - -type LaunchJsonSchema = { - version: string; - configurations: DebugConfiguration[]; -}; - -suite('Debugging - launch.json Updater Service', () => { - let helper: LaunchJsonUpdaterServiceHelper; - let getWorkspaceFolderStub: sinon.SinonStub; - let getActiveTextEditorStub: sinon.SinonStub; - let applyEditStub: sinon.SinonStub; - let executeCommandStub: sinon.SinonStub; - let debugConfigService: IDebugConfigurationService; - - const sandbox = sinon.createSandbox(); - setup(() => { - getWorkspaceFolderStub = sinon.stub(workspaceApis, 'getWorkspaceFolder'); - getActiveTextEditorStub = sinon.stub(windowApis, 'getActiveTextEditor'); - applyEditStub = sinon.stub(workspaceApis, 'applyEdit'); - executeCommandStub = sinon.stub(commandApis, 'executeCommand'); - - debugConfigService = mock(PythonDebugConfigurationService); - sandbox.stub(LaunchJsonUpdaterServiceHelper, 'isCommaImmediatelyBeforeCursor').returns(false); - helper = new LaunchJsonUpdaterServiceHelper(instance(debugConfigService)); - }); - teardown(() => { - sandbox.restore(); - sinon.restore(); - }); - - test('Configuration Array is detected as being empty', async () => { - const document = typemoq.Mock.ofType(); - const config: LaunchJsonSchema = { - version: '', - configurations: [], - }; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => JSON.stringify(config)); - - const isEmpty = LaunchJsonUpdaterServiceHelper.isConfigurationArrayEmpty(document.object); - assert.strictEqual(isEmpty, true); - }); - test('Configuration Array is not empty', async () => { - const document = typemoq.Mock.ofType(); - const config: LaunchJsonSchema = { - version: '', - configurations: [ - { - name: '', - request: 'launch', - type: 'python', - }, - ], - }; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => JSON.stringify(config)); - - const isEmpty = LaunchJsonUpdaterServiceHelper.isConfigurationArrayEmpty(document.object); - assert.strictEqual(isEmpty, false); - }); - test('Cursor is not positioned in the configurations array', async () => { - const document = typemoq.Mock.ofType(); - const config: LaunchJsonSchema = { - version: '', - configurations: [ - { - name: '', - request: 'launch', - type: 'python', - }, - ], - }; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => JSON.stringify(config)); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => 10); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, undefined); - }); - test('Cursor is positioned in the empty configurations array', async () => { - const document = typemoq.Mock.ofType(); - const json = `{ - "version": "0.1.0", - "configurations": [ - # Cursor Position - ] - }`; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.indexOf('#')); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, 'InsideEmptyArray'); - }); - test('Cursor is positioned before an item in the configurations array', async () => { - const document = typemoq.Mock.ofType(); - const json = `{ - "version": "0.1.0", - "configurations": [ - { - "name":"wow" - } - ] -}`; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.lastIndexOf('{') - 1); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, 'BeforeItem'); - }); - test('Cursor is positioned before an item in the middle of the configurations array', async () => { - const document = typemoq.Mock.ofType(); - const json = `{ - "version": "0.1.0", - "configurations": [ - { - "name":"wow" - },{ - "name":"wow" - } - ] -}`; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.indexOf(',{') + 1); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, 'BeforeItem'); - }); - test('Cursor is positioned after an item in the configurations array', async () => { - const document = typemoq.Mock.ofType(); - const json = `{ - "version": "0.1.0", - "configurations": [ - { - "name":"wow" - }] -}`; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.lastIndexOf('}]') + 1); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, 'AfterItem'); - }); - test('Cursor is positioned after an item in the middle of the configurations array', async () => { - const document = typemoq.Mock.ofType(); - const json = `{ - "version": "0.1.0", - "configurations": [ - { - "name":"wow" - },{ - "name":"wow" - } - ] -}`; - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.indexOf('},') + 1); - - const cursorPosition = LaunchJsonUpdaterServiceHelper.getCursorPositionInConfigurationsArray( - document.object, - new Position(0, 0), - ); - assert.strictEqual(cursorPosition, 'AfterItem'); - }); - test('Text to be inserted must be prefixed with a comma', async () => { - const config = {} as DebugConfiguration; - const expectedText = `,${JSON.stringify(config)}`; - - const textToInsert = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, 'AfterItem'); - - assert.strictEqual(textToInsert, expectedText); - }); - test('Text to be inserted must not be prefixed with a comma (as a comma already exists)', async () => { - const config = {} as DebugConfiguration; - const expectedText = JSON.stringify(config); - - const textToInsert = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, 'AfterItem', 'BeforeCursor'); - - assert.strictEqual(textToInsert, expectedText); - }); - test('Text to be inserted must be suffixed with a comma', async () => { - const config = {} as DebugConfiguration; - const expectedText = `${JSON.stringify(config)},`; - - const textToInsert = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, 'BeforeItem'); - - assert.strictEqual(textToInsert, expectedText); - }); - test('Text to be inserted must not be prefixed nor suffixed with commas', async () => { - const config = {} as DebugConfiguration; - const expectedText = JSON.stringify(config); - - const textToInsert = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, 'InsideEmptyArray'); - - assert.strictEqual(textToInsert, expectedText); - }); - test('When inserting the debug config into the json file format the document', async () => { - const json = `{ - "version": "0.1.0", - "configurations": [ - { - "name":"wow" - },{ - "name":"wow" - } - ] -}`; - const config = {} as DebugConfiguration; - const document = typemoq.Mock.ofType(); - document.setup((doc) => doc.getText(typemoq.It.isAny())).returns(() => json); - document.setup((doc) => doc.offsetAt(typemoq.It.isAny())).returns(() => json.indexOf('},') + 1); - applyEditStub.returns(undefined); - executeCommandStub.withArgs('editor.action.formatDocument').resolves(); - - await LaunchJsonUpdaterServiceHelper.insertDebugConfiguration(document.object, new Position(0, 0), config); - - sinon.assert.calledOnce(applyEditStub); - sinon.assert.calledOnce(executeCommandStub.withArgs('editor.action.formatDocument')); - }); - test('No changes to configuration if there is not active document', async () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - const { token } = new CancellationTokenSource(); - getActiveTextEditorStub.returns(undefined); - let debugConfigInserted = false; - LaunchJsonUpdaterServiceHelper.insertDebugConfiguration = async () => { - debugConfigInserted = true; - }; - - await helper.selectAndInsertDebugConfig(document.object, position, token); - - sinon.assert.calledOnce(getActiveTextEditorStub); - sinon.assert.notCalled(getWorkspaceFolderStub); - assert.strictEqual(debugConfigInserted, false); - }); - test('No changes to configuration if the active document is not same as the document passed in', async () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - const { token } = new CancellationTokenSource(); - const textEditor = typemoq.Mock.ofType(); - textEditor - .setup((t) => t.document) - .returns(() => ('x' as unknown) as TextDocument) - .verifiable(typemoq.Times.atLeastOnce()); - getActiveTextEditorStub.returns(textEditor.object); - let debugConfigInserted = false; - LaunchJsonUpdaterServiceHelper.insertDebugConfiguration = async () => { - debugConfigInserted = true; - }; - - await helper.selectAndInsertDebugConfig(document.object, position, token); - - sinon.assert.calledOnce(getActiveTextEditorStub); - sinon.assert.notCalled(getWorkspaceFolderStub); - textEditor.verifyAll(); - assert.strictEqual(debugConfigInserted, false); - }); - test('No changes to configuration if cancellation token has been cancelled', async () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - const tokenSource = new CancellationTokenSource(); - tokenSource.cancel(); - const { token } = tokenSource; - const textEditor = typemoq.Mock.ofType(); - const docUri = Uri.file(__filename); - const folderUri = Uri.file('Folder Uri'); - const folder = { name: '', index: 0, uri: folderUri }; - document - .setup((doc) => doc.uri) - .returns(() => docUri) - .verifiable(typemoq.Times.atLeastOnce()); - textEditor - .setup((t) => t.document) - .returns(() => document.object) - .verifiable(typemoq.Times.atLeastOnce()); - getActiveTextEditorStub.returns(textEditor.object); - getWorkspaceFolderStub.returns(folder); - when(debugConfigService.provideDebugConfigurations!(folder, token)).thenResolve(([''] as unknown) as void); - let debugConfigInserted = false; - LaunchJsonUpdaterServiceHelper.insertDebugConfiguration = async () => { - debugConfigInserted = true; - }; - - await helper.selectAndInsertDebugConfig(document.object, position, token); - - sinon.assert.calledOnce(getActiveTextEditorStub); - sinon.assert.calledOnce(getWorkspaceFolderStub); - textEditor.verifyAll(); - document.verifyAll(); - assert.strictEqual(debugConfigInserted, false); - }); - test('No changes to configuration if no configuration items are returned', async () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - const tokenSource = new CancellationTokenSource(); - const { token } = tokenSource; - const textEditor = typemoq.Mock.ofType(); - const docUri = Uri.file(__filename); - const folderUri = Uri.file('Folder Uri'); - const folder = { name: '', index: 0, uri: folderUri }; - document - .setup((doc) => doc.uri) - .returns(() => docUri) - .verifiable(typemoq.Times.atLeastOnce()); - textEditor - .setup((t) => t.document) - .returns(() => document.object) - .verifiable(typemoq.Times.atLeastOnce()); - - getActiveTextEditorStub.returns(textEditor.object); - getWorkspaceFolderStub.returns(folder); - - when(debugConfigService.provideDebugConfigurations!(folder, token)).thenResolve(([] as unknown) as void); - let debugConfigInserted = false; - LaunchJsonUpdaterServiceHelper.insertDebugConfiguration = async () => { - debugConfigInserted = true; - }; - - await helper.selectAndInsertDebugConfig(document.object, position, token); - - sinon.assert.calledOnce(getActiveTextEditorStub); - sinon.assert.calledOnce(getWorkspaceFolderStub.withArgs(docUri)); - textEditor.verifyAll(); - document.verifyAll(); - assert.strictEqual(debugConfigInserted, false); - }); - test('Changes are made to the configuration', async () => { - const document = typemoq.Mock.ofType(); - const position = new Position(0, 0); - const tokenSource = new CancellationTokenSource(); - const { token } = tokenSource; - const textEditor = typemoq.Mock.ofType(); - const docUri = Uri.file(__filename); - const folderUri = Uri.file('Folder Uri'); - const folder = { name: '', index: 0, uri: folderUri }; - document - .setup((doc) => doc.uri) - .returns(() => docUri) - .verifiable(typemoq.Times.atLeastOnce()); - textEditor - .setup((t) => t.document) - .returns(() => document.object) - .verifiable(typemoq.Times.atLeastOnce()); - getActiveTextEditorStub.returns(textEditor.object); - getWorkspaceFolderStub.withArgs(docUri).returns(folder); - when(debugConfigService.provideDebugConfigurations!(folder, token)).thenResolve(([ - 'config', - ] as unknown) as void); - let debugConfigInserted = false; - LaunchJsonUpdaterServiceHelper.insertDebugConfiguration = async () => { - debugConfigInserted = true; - }; - - await helper.selectAndInsertDebugConfig(document.object, position, token); - - sinon.assert.called(getActiveTextEditorStub); - sinon.assert.calledOnce(getWorkspaceFolderStub.withArgs(docUri)); - textEditor.verifyAll(); - document.verifyAll(); - assert.strictEqual(debugConfigInserted, true); - }); - test('If cursor is at the begining of line 1 then there is no comma before cursor', async () => { - sandbox.restore(); - const document = typemoq.Mock.ofType(); - const position = new Position(1, 0); - document - .setup((doc) => doc.lineAt(1)) - .returns(() => ({ range: new Range(1, 0, 1, 1) } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.getText(typemoq.It.isAny())) - .returns(() => '') - .verifiable(typemoq.Times.atLeastOnce()); - - const isBeforeCursor = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document.object, position); - - assert.ok(!isBeforeCursor); - document.verifyAll(); - }); - test('If cursor is positioned after some text (not a comma) then detect this', async () => { - sandbox.restore(); - const document = typemoq.Mock.ofType(); - const position = new Position(2, 2); - document - .setup((doc) => doc.lineAt(2)) - .returns(() => ({ range: new Range(2, 0, 1, 5) } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.getText(typemoq.It.isAny())) - .returns(() => 'Hello') - .verifiable(typemoq.Times.atLeastOnce()); - - const isBeforeCursor = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document.object, position); - - assert.ok(!isBeforeCursor); - document.verifyAll(); - }); - test('If cursor is positioned after a comma then detect this', async () => { - sandbox.restore(); - const document = typemoq.Mock.ofType(); - const position = new Position(2, 2); - document - .setup((doc) => doc.lineAt(2)) - .returns(() => ({ range: new Range(2, 0, 2, 3) } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.getText(typemoq.It.isAny())) - .returns(() => '}, ') - .verifiable(typemoq.Times.atLeastOnce()); - - const isBeforeCursor = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document.object, position); - - assert.ok(isBeforeCursor); - document.verifyAll(); - }); - test('If cursor is positioned in an empty line and previous line ends with comma, then detect this', async () => { - sandbox.restore(); - const document = typemoq.Mock.ofType(); - const position = new Position(2, 2); - document - .setup((doc) => doc.lineAt(1)) - .returns(() => ({ range: new Range(1, 0, 1, 3), text: '}, ' } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.lineAt(2)) - .returns(() => ({ range: new Range(2, 0, 2, 3), text: ' ' } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.getText(typemoq.It.isAny())) - .returns(() => ' ') - .verifiable(typemoq.Times.atLeastOnce()); - - const isBeforeCursor = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document.object, position); - - assert.ok(isBeforeCursor); - document.verifyAll(); - }); - test('If cursor is positioned in an empty line and previous line does not end with comma, then detect this', async () => { - sandbox.restore(); - const document = typemoq.Mock.ofType(); - const position = new Position(2, 2); - document - .setup((doc) => doc.lineAt(1)) - .returns(() => ({ range: new Range(1, 0, 1, 3), text: '} ' } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.lineAt(2)) - .returns(() => ({ range: new Range(2, 0, 2, 3), text: ' ' } as TextLine)) - .verifiable(typemoq.Times.atLeastOnce()); - document - .setup((doc) => doc.getText(typemoq.It.isAny())) - .returns(() => ' ') - .verifiable(typemoq.Times.atLeastOnce()); - - const isBeforeCursor = LaunchJsonUpdaterServiceHelper.isCommaImmediatelyBeforeCursor(document.object, position); - - assert.ok(!isBeforeCursor); - document.verifyAll(); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts deleted file mode 100644 index 8a5898611c82..000000000000 --- a/src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'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 { DebugConfigStrings } from '../../../../../client/common/utils/localize'; -import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; -import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; -import * as djangoLaunch from '../../../../../client/debugger/extension/configuration/providers/djangoLaunch'; -import * as workspaceApis from '../../../../../client/common/vscodeApis/workspaceApis'; - -suite('Debugging - Configuration Provider Django', () => { - let pathExistsStub: sinon.SinonStub; - let pathSeparatorStub: sinon.SinonStub; - let workspaceStub: sinon.SinonStub; - let input: MultiStepInput; - - setup(() => { - input = mock>(MultiStepInput); - pathExistsStub = sinon.stub(fs, 'pathExists'); - pathSeparatorStub = sinon.stub(path, 'sep'); - workspaceStub = sinon.stub(workspaceApis, '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'); - 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'); - 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 }; - 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 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 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 }; - 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 }; - 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 }; - 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 }; - 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 selected managepy path', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve('hello'); - await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: 'hello', - args: ['runserver'], - django: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - const workspaceFolderToken = '${workspaceFolder}'; - const defaultProgram = `${workspaceFolderToken}-manage.py`; - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve(); - await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: defaultProgram, - args: ['runserver'], - django: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts deleted file mode 100644 index 80ce37167024..000000000000 --- a/src/test/debugger/extension/configuration/providers/fastapiLaunch.unit.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from 'vscode'; -import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; -import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; -import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import * as fastApiLaunch from '../../../../../client/debugger/extension/configuration/providers/fastapiLaunch'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider FastAPI', () => { - let input: MultiStepInput; - let pathExistsStub: sinon.SinonStub; - - setup(() => { - input = mock>(MultiStepInput); - 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'); - 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'); - 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 }; - - await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.fastapi.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'uvicorn', - args: ['main:app', '--reload'], - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - - when(input.showInputBox(anything())).thenResolve('main'); - - await fastApiLaunch.buildFastAPILaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.fastapi.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'uvicorn', - args: ['main:app', '--reload'], - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts deleted file mode 100644 index f627c7558c51..000000000000 --- a/src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -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 { buildFileLaunchDebugConfiguration } from '../../../../../client/debugger/extension/configuration/providers/fileLaunch'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider File', () => { - 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 buildFileLaunchDebugConfiguration( - (undefined as unknown) as MultiStepInput, - state, - ); - - const config = { - name: DebugConfigStrings.file.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: '${file}', - console: 'integratedTerminal', - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts deleted file mode 100644 index 08fb5259b282..000000000000 --- a/src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from 'vscode'; -import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; -import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; -import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import * as flaskLaunch from '../../../../../client/debugger/extension/configuration/providers/flaskLaunch'; - -suite('Debugging - Configuration Provider Flask', () => { - let pathExistsStub: sinon.SinonStub; - let input: MultiStepInput; - setup(() => { - input = mock>(MultiStepInput); - 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'); - 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'); - 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 }; - - await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.flask.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: 'app.py', - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - - when(input.showInputBox(anything())).thenResolve('hello'); - - await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.flask.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: 'hello', - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - when(input.showInputBox(anything())).thenResolve(); - - await flaskLaunch.buildFlaskLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.flask.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: 'app.py', - FLASK_DEBUG: '1', - }, - args: ['run', '--no-debugger', '--no-reload'], - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts deleted file mode 100644 index 2508db506ca2..000000000000 --- a/src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -import { anything, instance, mock, 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 { buildModuleLaunchConfiguration } from '../../../../../client/debugger/extension/configuration/providers/moduleLaunch'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider Module', () => { - 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 }; - const input = mock>(MultiStepInput); - - when(input.showInputBox(anything())).thenResolve(); - - await buildModuleLaunchConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.module.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: DebugConfigStrings.module.snippet.default, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - test('Launch JSON with selected module name', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - const input = mock>(MultiStepInput); - - when(input.showInputBox(anything())).thenResolve('hello'); - - await buildModuleLaunchConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.module.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'hello', - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts deleted file mode 100644 index 8217e150aa01..000000000000 --- a/src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import * as path from 'path'; -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 { buildPidAttachConfiguration } from '../../../../../client/debugger/extension/configuration/providers/pidAttach'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; - -suite('Debugging - Configuration Provider File', () => { - 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 buildPidAttachConfiguration((undefined as unknown) as MultiStepInput, state); - - const config = { - name: DebugConfigStrings.attachPid.snippet.name, - type: DebuggerTypeName, - request: 'attach', - processId: '${command:pickProcess}', - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts b/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts deleted file mode 100644 index 688215259a2f..000000000000 --- a/src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 } from 'vscode'; -import { DebugConfigStrings } from '../../../../../client/common/utils/localize'; -import { MultiStepInput } from '../../../../../client/common/utils/multiStepInput'; -import { DebuggerTypeName } from '../../../../../client/debugger/constants'; -import { resolveVariables } from '../../../../../client/debugger/extension/configuration/utils/common'; -import * as pyramidLaunch from '../../../../../client/debugger/extension/configuration/providers/pyramidLaunch'; -import { DebugConfigurationState } from '../../../../../client/debugger/extension/types'; -import * as workspaceApis from '../../../../../client/common/vscodeApis/workspaceApis'; - -suite('Debugging - Configuration Provider Pyramid', () => { - let input: MultiStepInput; - let pathExistsStub: sinon.SinonStub; - let pathSeparatorStub: sinon.SinonStub; - let workspaceStub: sinon.SinonStub; - - setup(() => { - input = mock>(MultiStepInput); - pathExistsStub = sinon.stub(fs, 'pathExists'); - pathSeparatorStub = sinon.stub(path, 'sep'); - workspaceStub = sinon.stub(workspaceApis, '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'); - 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'); - 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 }; - 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 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 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 }; - 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 }; - 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 }; - 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 not return errors if resolved path is ini', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - 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 }; - pathSeparatorStub.value('-'); - - await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.pyramid.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: ['${workspaceFolder}-development.ini'], - pyramid: true, - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve('hello'); - - await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.pyramid.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: ['hello'], - pyramid: true, - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); - 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 }; - const workspaceFolderToken = '${workspaceFolder}'; - const defaultIni = `${workspaceFolderToken}-development.ini`; - - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve(); - - await pyramidLaunch.buildPyramidLaunchConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.pyramid.snippet.name, - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: [defaultIni], - pyramid: true, - jinja: true, - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - }); -}); diff --git a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts b/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts deleted file mode 100644 index 323cda94a1eb..000000000000 --- a/src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -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 * 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'; - -suite('Debugging - Configuration Provider Remote Attach', () => { - let input: MultiStepInput; - - setup(() => { - input = mock>(MultiStepInput); - }); - teardown(() => { - sinon.restore(); - }); - test('Configure port will display prompt', async () => { - when(input.showInputBox(anything())).thenResolve(); - - await configuration.configurePort(instance(input), {}); - - verify(input.showInputBox(anything())).once(); - }); - test('Configure port will default to 5678 if entered value is not a number', async () => { - const config: { connect?: { port?: number } } = {}; - when(input.showInputBox(anything())).thenResolve('xyz'); - - await configuration.configurePort(instance(input), config); - - verify(input.showInputBox(anything())).once(); - expect(config).to.be.deep.equal({ connect: { port: 5678 } }); - }); - test('Configure port will default to 5678', async () => { - const config: { connect?: { port?: number } } = {}; - when(input.showInputBox(anything())).thenResolve(); - - await configuration.configurePort(instance(input), config); - - verify(input.showInputBox(anything())).once(); - expect(config).to.be.deep.equal({ connect: { port: 5678 } }); - }); - test('Configure port will use user selected value', async () => { - const config: { connect?: { port?: number } } = {}; - when(input.showInputBox(anything())).thenResolve('1234'); - - await configuration.configurePort(instance(input), config); - - verify(input.showInputBox(anything())).once(); - expect(config).to.be.deep.equal({ connect: { port: 1234 } }); - }); - test('Launch JSON with default host name', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - let portConfigured = false; - when(input.showInputBox(anything())).thenResolve(); - - sinon.stub(configuration, 'configurePort').callsFake(async () => { - portConfigured = true; - }); - - const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); - if (configurePort) { - await configurePort!(input, state); - } - - const config = { - name: DebugConfigStrings.attach.snippet.name, - type: DebuggerTypeName, - request: 'attach', - connect: { - host: 'localhost', - port: 5678, - }, - pathMappings: [ - { - localRoot: '${workspaceFolder}', - remoteRoot: '.', - }, - ], - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - expect(portConfigured).to.be.equal(true, 'Port not configured'); - }); - test('Launch JSON with user defined host name', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - let portConfigured = false; - when(input.showInputBox(anything())).thenResolve('Hello'); - sinon.stub(configuration, 'configurePort').callsFake(async (_, cfg) => { - portConfigured = true; - cfg.connect!.port = 9999; - }); - const configurePort = await remoteAttach.buildRemoteAttachConfiguration(instance(input), state); - if (configurePort) { - await configurePort(input, state); - } - - const config = { - name: DebugConfigStrings.attach.snippet.name, - type: DebuggerTypeName, - request: 'attach', - connect: { - host: 'Hello', - port: 9999, - }, - pathMappings: [ - { - localRoot: '${workspaceFolder}', - remoteRoot: '.', - }, - ], - justMyCode: true, - }; - - expect(state.config).to.be.deep.equal(config); - expect(portConfigured).to.be.equal(true, 'Port not configured'); - }); -}); diff --git a/src/test/debugger/extension/serviceRegistry.unit.test.ts b/src/test/debugger/extension/serviceRegistry.unit.test.ts index 43d81bbe1385..056d722c7e0e 100644 --- a/src/test/debugger/extension/serviceRegistry.unit.test.ts +++ b/src/test/debugger/extension/serviceRegistry.unit.test.ts @@ -11,10 +11,6 @@ import { DebugSessionLoggingFactory } from '../../../client/debugger/extension/a import { OutdatedDebuggerPromptFactory } from '../../../client/debugger/extension/adapter/outdatedDebuggerPrompt'; import { AttachProcessProviderFactory } from '../../../client/debugger/extension/attachQuickPick/factory'; import { IAttachProcessProviderFactory } from '../../../client/debugger/extension/attachQuickPick/types'; -import { PythonDebugConfigurationService } from '../../../client/debugger/extension/configuration/debugConfigurationService'; -import { LaunchJsonCompletionProvider } from '../../../client/debugger/extension/configuration/launch.json/completionProvider'; -import { InterpreterPathCommand } from '../../../client/debugger/extension/configuration/launch.json/interpreterPathCommand'; -import { LaunchJsonUpdaterService } from '../../../client/debugger/extension/configuration/launch.json/updaterService'; import { AttachConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/attach'; import { LaunchConfigurationResolver } from '../../../client/debugger/extension/configuration/resolvers/launch'; import { IDebugConfigurationResolver } from '../../../client/debugger/extension/configuration/types'; @@ -25,7 +21,6 @@ import { IChildProcessAttachService, IDebugSessionEventHandlers } from '../../.. import { registerTypes } from '../../../client/debugger/extension/serviceRegistry'; import { IDebugAdapterDescriptorFactory, - IDebugConfigurationService, IDebugSessionLoggingFactory, IOutdatedDebuggerPromptFactory, } from '../../../client/debugger/extension/types'; @@ -35,43 +30,18 @@ import { IServiceManager } from '../../../client/ioc/types'; suite('Debugging - Service Registry', () => { let serviceManager: IServiceManager; - setup(() => { serviceManager = mock(ServiceManager); }); test('Registrations', () => { registerTypes(instance(serviceManager)); - verify( - serviceManager.addSingleton( - IExtensionSingleActivationService, - InterpreterPathCommand, - ), - ).once(); - verify( - serviceManager.addSingleton( - IDebugConfigurationService, - PythonDebugConfigurationService, - ), - ).once(); verify( serviceManager.addSingleton( IChildProcessAttachService, ChildProcessAttachService, ), ).once(); - verify( - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonCompletionProvider, - ), - ).once(); - verify( - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonUpdaterService, - ), - ).once(); verify( serviceManager.addSingleton( IExtensionSingleActivationService, diff --git a/src/test/debugger/extension/configuration/launch.json/interpreterPathCommand.unit.test.ts b/src/test/interpreters/interpreterPathCommand.unit.test.ts similarity index 85% rename from src/test/debugger/extension/configuration/launch.json/interpreterPathCommand.unit.test.ts rename to src/test/interpreters/interpreterPathCommand.unit.test.ts index 77077ad945fb..7001453100ec 100644 --- a/src/test/debugger/extension/configuration/launch.json/interpreterPathCommand.unit.test.ts +++ b/src/test/interpreters/interpreterPathCommand.unit.test.ts @@ -8,11 +8,11 @@ import * as sinon from 'sinon'; import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; -import { IDisposable } from '../../../../../client/common/types'; -import * as commandApis from '../../../../../client/common/vscodeApis/commandApis'; -import { InterpreterPathCommand } from '../../../../../client/debugger/extension/configuration/launch.json/interpreterPathCommand'; -import { IInterpreterService } from '../../../../../client/interpreter/contracts'; -import { PythonEnvironment } from '../../../../../client/pythonEnvironments/info'; +import { IDisposable } from '../../client/common/types'; +import * as commandApis from '../../client/common/vscodeApis/commandApis'; +import { InterpreterPathCommand } from '../../client/interpreter/interpreterPathCommand'; +import { IInterpreterService } from '../../client/interpreter/contracts'; +import { PythonEnvironment } from '../../client/pythonEnvironments/info'; suite('Interpreter Path Command', () => { let interpreterService: IInterpreterService; diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index 00090eb4b6e9..bb488a49307d 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -43,6 +43,7 @@ import { ActivatedEnvironmentLaunch } from '../../client/interpreter/virtualEnvs import { CondaInheritEnvPrompt } from '../../client/interpreter/virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentPrompt } from '../../client/interpreter/virtualEnvs/virtualEnvPrompt'; import { ServiceManager } from '../../client/ioc/serviceManager'; +import { InterpreterPathCommand } from '../../client/interpreter/interpreterPathCommand'; suite('Interpreters - Service Registry', () => { test('Registrations', () => { @@ -74,6 +75,7 @@ suite('Interpreters - Service Registry', () => { [EnvironmentActivationService, EnvironmentActivationService], [IEnvironmentActivationService, EnvironmentActivationService], + [IExtensionSingleActivationService, InterpreterPathCommand], [IExtensionActivationService, CondaInheritEnvPrompt], [IActivatedEnvironmentLaunch, ActivatedEnvironmentLaunch], ].forEach((mapping) => {