diff --git a/src/client/common/variables/systemVariables.ts b/src/client/common/variables/systemVariables.ts index 65c90e111efb..ee8af4400e9c 100644 --- a/src/client/common/variables/systemVariables.ts +++ b/src/client/common/variables/systemVariables.ts @@ -3,44 +3,6 @@ import * as Types from './sysTypes'; import { IStringDictionary, ISystemVariables } from './types'; /* tslint:disable:rule1 no-any no-unnecessary-callback-wrapper jsdoc-format no-for-in prefer-const no-increment-decrement */ -export abstract class Parser { - - protected static merge(destination: T, source: T, overwrite: boolean): void { - Object.keys(source).forEach((key) => { - const destValue = (destination as any as { [key: string]: string })[key]; - const sourceValue = (source as any as { [key: string]: string })[key]; - if (Types.isUndefined(sourceValue)) { - return; - } - if (Types.isUndefined(destValue)) { - (destination as any as { [key: string]: string })[key] = sourceValue; - } else { - if (overwrite) { - if (Types.isObject(destValue) && Types.isObject(sourceValue)) { - this.merge(destValue, sourceValue, overwrite); - } else { - (destination as any as { [key: string]: string })[key] = sourceValue; - } - } - } - }); - } - - // tslint:disable-next-line:no-empty - protected log(message: string): void { } - - // tslint:disable-next-line:no-any - protected is(value: any, func: (value: any) => boolean, wrongTypeState?: any, wrongTypeMessage?: string, undefinedState?: any, undefinedMessage?: string): boolean { - if (Types.isUndefined(value)) { - return false; - } - if (!func(value)) { - return false; - } - return true; - } -} - export abstract class AbstractSystemVariables implements ISystemVariables { public resolve(value: string): string; diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 11b56f28e417..8e5f549cce04 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -44,8 +44,10 @@ export interface ExceptionHandling { unhandled: string[]; } +export type DebuggerType = 'python' | 'pythonExperimental'; + export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { - type?: 'python' | 'pythonExperimental'; + type?: DebuggerType; /** An absolute path to the program to debug. */ module?: string; program: string; diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts new file mode 100644 index 000000000000..eda39434156c --- /dev/null +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable, unmanaged } from 'inversify'; +import * as path from 'path'; +import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; +import { IDocumentManager, IWorkspaceService } from '../../common/application/types'; +import { PythonLanguage } from '../../common/constants'; +import { IConfigurationService } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { DebuggerType, LaunchRequestArguments } from '../Common/Contracts'; + +// tslint:disable:no-invalid-template-strings + +export type PythonDebugConfiguration = DebugConfiguration & LaunchRequestArguments; + +@injectable() +export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { + constructor(@unmanaged() public debugType: DebuggerType, private serviceContainer: IServiceContainer) { } + public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { + const config = debugConfiguration as PythonDebugConfiguration; + const numberOfSettings = Object.keys(config); + const provideDefaultConfigSettings = (config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0; + const workspaceFolder = this.getWorkspaceFolder(folder, config); + if (!provideDefaultConfigSettings) { + this.resolveAndUpdatePythonPath(workspaceFolder, config); + return config; + } + + const configService = this.serviceContainer.get(IConfigurationService); + const pythonPath = configService.getSettings(workspaceFolder).pythonPath; + const defaultProgram = this.getProgram(config); + const envFile = workspaceFolder ? path.join(workspaceFolder.fsPath, '.env') : ''; + + config.name = 'Launch'; + config.type = this.debugType; + config.request = 'launch'; + config.pythonPath = pythonPath; + config.program = defaultProgram ? defaultProgram : ''; + config.cwd = workspaceFolder ? workspaceFolder.fsPath : undefined; + config.envFile = envFile; + config.env = {}; + config.debugOptions = []; + + this.provideDefaults(config); + return config; + } + protected abstract provideDefaults(debugConfiguration: PythonDebugConfiguration): void; + private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonDebugConfiguration): Uri | undefined { + if (folder) { + return folder.uri; + } + const program = this.getProgram(config); + const workspaceService = this.serviceContainer.get(IWorkspaceService); + if (!Array.isArray(workspaceService.workspaceFolders) || workspaceService.workspaceFolders.length === 0) { + return program ? Uri.file(path.dirname(program)) : undefined; + } + if (workspaceService.workspaceFolders.length === 1) { + return workspaceService.workspaceFolders[0].uri; + } + if (program) { + const workspaceFolder = workspaceService.getWorkspaceFolder(Uri.file(program)); + if (workspaceFolder) { + return workspaceFolder.uri; + } + } + } + private getProgram(config: PythonDebugConfiguration): string | undefined { + const documentManager = this.serviceContainer.get(IDocumentManager); + const editor = documentManager.activeTextEditor; + if (editor && editor.document.languageId === PythonLanguage.language) { + return editor.document.fileName; + } + } + private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void { + if (!debugConfiguration || debugConfiguration.pythonPath !== '${config:python.pythonPath}') { + return; + } + const configService = this.serviceContainer.get(IConfigurationService); + const pythonPath = configService.getSettings(workspaceFolder).pythonPath; + debugConfiguration.pythonPath = pythonPath; + } +} diff --git a/src/client/debugger/configProviders/pyhtonV2Provider.ts b/src/client/debugger/configProviders/pyhtonV2Provider.ts new file mode 100644 index 000000000000..04d637155c31 --- /dev/null +++ b/src/client/debugger/configProviders/pyhtonV2Provider.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../ioc/types'; +import { BaseConfigurationProvider, PythonDebugConfiguration } from './baseProvider'; + +@injectable() +export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('pythonExperimental', serviceContainer); + } + protected provideDefaults(debugConfiguration: PythonDebugConfiguration): void { + debugConfiguration.stopOnEntry = false; + debugConfiguration.console = 'integratedTerminal'; + } +} diff --git a/src/client/debugger/configProviders/pythonProvider.ts b/src/client/debugger/configProviders/pythonProvider.ts new file mode 100644 index 000000000000..434967ab8c69 --- /dev/null +++ b/src/client/debugger/configProviders/pythonProvider.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../ioc/types'; +import { BaseConfigurationProvider, PythonDebugConfiguration } from './baseProvider'; + +@injectable() +export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super('python', serviceContainer); + } + protected provideDefaults(debugConfiguration: PythonDebugConfiguration): void { + debugConfiguration.stopOnEntry = true; + debugConfiguration.debugOptions = [ + 'RedirectOutput' + ]; + } +} diff --git a/src/client/debugger/configProviders/serviceRegistry.ts b/src/client/debugger/configProviders/serviceRegistry.ts new file mode 100644 index 000000000000..d664bfac771c --- /dev/null +++ b/src/client/debugger/configProviders/serviceRegistry.ts @@ -0,0 +1,15 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { DebugConfigurationProvider } from 'vscode'; +import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '..'; +import { IServiceManager } from '../../ioc/types'; +import { IDebugConfigurationProvider } from '../types'; + +export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IDebugConfigurationProvider, PythonDebugConfigurationProvider); + serviceManager.addSingleton(IDebugConfigurationProvider, PythonV2DebugConfigurationProvider); +} diff --git a/src/client/debugger/configProviders/simpleProvider.ts b/src/client/debugger/configProviders/simpleProvider.ts deleted file mode 100644 index 1be85b735f82..000000000000 --- a/src/client/debugger/configProviders/simpleProvider.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as path from 'path'; -import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, window, workspace, WorkspaceFolder } from 'vscode'; -import { PythonSettings } from '../../common/configSettings'; - -type PythonDebugConfiguration = DebugConfiguration & { - stopOnEntry?: boolean, - pythonPath?: string, - program?: string, - cwd?: string, - env?: object, - envFile?: string, - debugOptions?: string[] -}; - -export class SimpleConfigurationProvider implements DebugConfigurationProvider { - private getProgram(config: PythonDebugConfiguration): string | undefined { - const editor = window.activeTextEditor; - if (editor && editor.document.languageId === 'python') { - return editor.document.fileName; - } - return undefined; - } - private getWorkspaceFolder(config: PythonDebugConfiguration): string | undefined { - const program = this.getProgram(config); - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - return program ? path.dirname(program) : undefined; - } - if (workspace.workspaceFolders.length === 1) { - return workspace.workspaceFolders[0].uri.fsPath; - } - if (program) { - const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(program)); - if (workspaceFolder) { - return workspaceFolder.uri.fsPath; - } - } - - return undefined; - } - // tslint:disable-next-line:member-ordering - public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { - const keys = Object.keys(debugConfiguration); - const provideConfig = (debugConfiguration.noDebug === true && keys.length === 1) || keys.length === 0; - if (!provideConfig) { - return debugConfiguration; - } - const config = debugConfiguration as PythonDebugConfiguration; - const defaultProgram = this.getProgram(config); - const workspaceFolder = this.getWorkspaceFolder(config); - const envFile = workspaceFolder ? path.join(workspaceFolder, '.env') : undefined; - return { - name: 'Launch', - type: 'python', - request: 'launch', - stopOnEntry: true, - pythonPath: PythonSettings.getInstance(workspaceFolder ? Uri.file(workspaceFolder) : undefined).pythonPath, - program: defaultProgram, - cwd: workspaceFolder, - envFile, - env: {}, - debugOptions: [ - 'RedirectOutput' - ], - noDebug: debugConfiguration.noDebug - }; - } -} diff --git a/src/client/debugger/index.ts b/src/client/debugger/index.ts index 9065d5fdeb02..245a69691b63 100644 --- a/src/client/debugger/index.ts +++ b/src/client/debugger/index.ts @@ -1 +1,2 @@ -export * from './configProviders/simpleProvider'; +export * from './configProviders/pythonProvider'; +export * from './configProviders/pyhtonV2Provider'; diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 4bb931ca8a91..c34031b082af 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -36,3 +36,5 @@ export const IProtocolMessageWriter = Symbol('IProtocolMessageWriter'); export interface IProtocolMessageWriter { write(stream: Socket | NodeJS.WriteStream, message: Message): void; } + +export const IDebugConfigurationProvider = Symbol('DebugConfigurationProvider'); diff --git a/src/client/extension.ts b/src/client/extension.ts index 489543063bfe..e9cc4ee998bd 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -6,7 +6,6 @@ if ((Reflect as any).metadata === undefined) { require('reflect-metadata'); } import { Container } from 'inversify'; -import * as os from 'os'; import * as vscode from 'vscode'; import { Disposable, Memento, OutputChannel, window } from 'vscode'; import { BannerService } from './banner'; @@ -23,7 +22,9 @@ import { IProcessService } from './common/process/types'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { GLOBAL_MEMENTO, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; -import { SimpleConfigurationProvider } from './debugger'; +import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; +import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; +import { IDebugConfigurationProvider } from './debugger/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { InterpreterSelector } from './interpreter/configuration/interpreterSelector'; import { ICondaService, IInterpreterService, IInterpreterVersionService } from './interpreter/contracts'; @@ -92,6 +93,7 @@ export async function activate(context: vscode.ExtensionContext) { platformRegisterTypes(serviceManager); installerRegisterTypes(serviceManager); commonRegisterTerminalTypes(serviceManager); + debugConfigurationRegisterTypes(serviceManager); serviceManager.get(ICodeExecutionManager).registerCommands(); @@ -193,11 +195,9 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); - // In case we have CR LF - const triggerCharacters: string[] = os.EOL.split(''); - triggerCharacters.shift(); - - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('python', new SimpleConfigurationProvider())); + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); + }); activationDeferred.resolve(); // tslint:disable-next-line:no-unused-expression diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts new file mode 100644 index 000000000000..6f894cca86a2 --- /dev/null +++ b/src/test/debugger/configProvider/provider.test.ts @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-invalid-template-strings no-any + +import { expect } from 'chai'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; +import { IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; +import { PythonLanguage } from '../../../client/common/constants'; +import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; +import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; +import { IServiceContainer } from '../../../client/ioc/types'; + +[ + { debugType: 'pythonExperimental', class: PythonV2DebugConfigurationProvider }, + { debugType: 'python', class: PythonDebugConfigurationProvider } +].forEach(provider => { + suite(`Debugging - Config Provider ${provider.debugType}`, () => { + let serviceContainer: TypeMoq.IMock; + let debugProvider: DebugConfigurationProvider; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + debugProvider = new provider.class(serviceContainer.object); + }); + function createMoqWorkspaceFolder(folderPath: string) { + const folder = TypeMoq.Mock.ofType(); + folder.setup(f => f.uri).returns(() => Uri.file(folderPath)); + return folder.object; + } + function registerPythonPath(pythonPath: string) { + const confgService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => confgService.object); + const settings = TypeMoq.Mock.ofType(); + settings.setup(s => s.pythonPath).returns(() => pythonPath); + confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + } + function setupActiveEditor(fileName: string | undefined, languageId: string) { + const documentManager = TypeMoq.Mock.ofType(); + if (fileName) { + const textEditor = TypeMoq.Mock.ofType(); + const document = TypeMoq.Mock.ofType(); + document.setup(d => d.languageId).returns(() => languageId); + document.setup(d => d.fileName).returns(() => fileName); + textEditor.setup(t => t.document).returns(() => document.object); + documentManager.setup(d => d.activeTextEditor).returns(() => textEditor.object); + } else { + documentManager.setup(d => d.activeTextEditor).returns(() => undefined); + } + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager))).returns(() => documentManager.object); + } + function setupWorkspaces(folders: string[]) { + const workspaceService = TypeMoq.Mock.ofType(); + const workspaceFolders = folders.map(createMoqWorkspaceFolder); + workspaceService.setup(w => w.workspaceFolders).returns(() => workspaceFolders); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + } + test('Defaults should be returned when an empty object is passed with a Workspace Folder and active file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd', __dirname); + expect(debugConfig).to.have.property('envFile', path.join(__dirname, '.env')); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Defaults should be returned when an object with \'noDebug\' property is passed with a Workspace Folder and active file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { noDebug: true } as any as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd', __dirname); + expect(debugConfig).to.have.property('envFile', path.join(__dirname, '.env')); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const pythonFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd', Uri.file(path.dirname('')).fsPath); + expect(debugConfig).to.have.property('envFile', path.join(Uri.file(path.dirname('')).fsPath, '.env')); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + registerPythonPath(pythonPath); + setupActiveEditor(undefined, PythonLanguage.language); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', ''); + expect(debugConfig).to.have.property('cwd', undefined); + expect(debugConfig).to.have.property('envFile', ''); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const activeFile = 'xyz.js'; + registerPythonPath(pythonPath); + setupActiveEditor(activeFile, 'javascript'); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', ''); + expect(debugConfig).to.have.property('cwd', undefined); + expect(debugConfig).to.have.property('envFile', ''); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const activeFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.have.property('type', provider.debugType); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', activeFile); + expect(debugConfig).to.have.property('cwd', Uri.file(defaultWorkspace).fsPath); + expect(debugConfig).to.have.property('envFile', path.join(Uri.file(defaultWorkspace).fsPath, '.env')); + expect(debugConfig).to.have.property('env'); + // tslint:disable-next-line:no-any + expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); + }); + test('Ensure `${config:python.pythonPath}` is replaced with actual pythonPath', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + registerPythonPath(pythonPath); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { pythonPath: '${config:python.pythonPath}' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('pythonPath', pythonPath); + }); + test('Ensure hardcoded pythonPath is left unaltered', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + registerPythonPath(pythonPath); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugPythonPath = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { pythonPath: debugPythonPath } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('pythonPath', debugPythonPath); + }); + test('Test defaults of experimental debugger', async () => { + if (provider.debugType !== 'pythonExperimental') { + return; + } + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); + + expect(debugConfig).to.have.property('console', 'integratedTerminal'); + expect(debugConfig).to.have.property('stopOnEntry', false); + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).to.be.deep.equal([]); + }); + test('Test defaults of python debugger', async () => { + if (provider.debugType !== 'python') { + return; + } + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + registerPythonPath(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); + + expect(debugConfig).to.have.property('stopOnEntry', true); + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).to.be.deep.equal(['RedirectOutput']); + }); + }); +});