From ae5120aa16c90bf828fd694d46615b4723e92853 Mon Sep 17 00:00:00 2001 From: Pavel Minaev Date: Mon, 9 Nov 2020 12:22:46 -0800 Subject: [PATCH] Fix #14674: Enable overriding "pythonPath" in the launcher Fix #12462: Update launch.json schema to add "python" and remove "pythonPath" Split the "pythonPath" debug property into "python", "debugAdapterPython", and "debugLauncherPython". Properly register the python.interpreterPath command to expand the ${command:interpreterPath} debug configuration variable. Do most debug config validation on fully expanded property values via resolveDebugConfigurationWithSubstitutedVariables(). Add fixups for legacy launch.json with "pythonPath" and/or "${command:python.interpreterPath}". --- package.json | 11 +- .../checks/invalidLaunchJsonDebugger.ts | 14 +- .../debugger/extension/adapter/factory.ts | 12 +- .../debugConfigurationService.ts | 16 +- .../configuration/resolvers/attach.ts | 4 +- .../extension/configuration/resolvers/base.ts | 30 +- .../configuration/resolvers/launch.ts | 77 +++-- .../debugger/extension/configuration/types.ts | 6 + src/client/debugger/types.ts | 20 +- src/client/testing/common/debugLauncher.ts | 12 +- .../resolvers/attach.unit.test.ts | 197 ++++++----- .../configuration/resolvers/base.unit.test.ts | 18 +- .../resolvers/launch.unit.test.ts | 305 ++++++++++++------ .../testing/common/debugLauncher.unit.test.ts | 17 +- 14 files changed, 504 insertions(+), 235 deletions(-) diff --git a/package.json b/package.json index b48a3c400202..388f42db8201 100644 --- a/package.json +++ b/package.json @@ -1555,7 +1555,8 @@ "python" ], "variables": { - "pickProcess": "python.pickLocalProcess" + "pickProcess": "python.pickLocalProcess", + "interpreterPath": "python.interpreterPath" }, "configurationSnippets": [], "configurationAttributes": { @@ -1571,10 +1572,10 @@ "description": "Absolute path to the program.", "default": "${file}" }, - "pythonPath": { + "python": { "type": "string", - "description": "Path (fully qualified) to python executable. Defaults to the value in settings", - "default": "${command:python.interpreterPath}" + "description": "Absolute path to the Python interpreter executable; overrides workspace configuration if set.", + "default": "${command:interpreterPath}" }, "pythonArgs": { "type": "array", @@ -3685,4 +3686,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} diff --git a/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts b/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts index a545764d26ec..7c4f45a286e1 100644 --- a/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts +++ b/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts @@ -114,8 +114,10 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { diagnostics.push(new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConsoleTypeDiagnostic, resource)); } if ( + fileContents.indexOf('"pythonPath":') > 0 || fileContents.indexOf('{config:python.pythonPath}') > 0 || - fileContents.indexOf('{config:python.interpreterPath}') > 0 + fileContents.indexOf('{config:python.interpreterPath}') > 0 || + fileContents.indexOf('{command:python.interpreterPath}') > 0 ) { diagnostics.push( new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConfigPythonPathDiagnostic, resource, false) @@ -169,15 +171,21 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { break; } case DiagnosticCodes.ConfigPythonPathDiagnostic: { + fileContents = this.findAndReplace(fileContents, '"pythonPath":', '"python":'); fileContents = this.findAndReplace( fileContents, '{config:python.pythonPath}', - '{command:python.interpreterPath}' + '{command:interpreterPath}' ); fileContents = this.findAndReplace( fileContents, '{config:python.interpreterPath}', - '{command:python.interpreterPath}' + '{command:interpreterPath}' + ); + fileContents = this.findAndReplace( + fileContents, + '{command:python.interpreterPath}', + '{command:interpreterPath}' ); break; } diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 6379874d8e47..3ef8e87f493e 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -26,7 +26,8 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IApplicationShell) private readonly appShell: IApplicationShell - ) {} + ) { } + public async createDebugAdapterDescriptor( session: DebugSession, _executable: DebugAdapterExecutable | undefined @@ -54,7 +55,7 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac } } - const pythonPath = await this.getPythonPath(configuration, session.workspaceFolder); + const pythonPath = await this.getDebugAdapterPython(configuration, session.workspaceFolder); if (pythonPath.length !== 0) { if (configuration.request === 'attach' && configuration.processId !== undefined) { sendTelemetryEvent(EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS); @@ -96,13 +97,16 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac * @returns {Promise} Path to the python interpreter for this workspace. * @memberof DebugAdapterDescriptorFactory */ - private async getPythonPath( + private async getDebugAdapterPython( configuration: LaunchRequestArguments | AttachRequestArguments, workspaceFolder?: WorkspaceFolder ): Promise { - if (configuration.pythonPath) { + if (configuration.debugAdapterPython !== undefined) { + return configuration.debugAdapterPython; + } else if (configuration.pythonPath) { return configuration.pythonPath; } + const resourceUri = workspaceFolder ? workspaceFolder.uri : undefined; const interpreter = await this.interpreterService.getActiveInterpreter(resourceUri); if (interpreter) { diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts index 655de8b6a5df..5736e43391a0 100644 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/debugConfigurationService.ts @@ -28,7 +28,8 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationProviderFactory) private readonly providerFactory: IDebugConfigurationProviderFactory, @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory - ) {} + ) { } + public async provideDebugConfigurations( folder: WorkspaceFolder | undefined, token?: CancellationToken @@ -46,6 +47,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi return [state.config as DebugConfiguration]; } } + public async resolveDebugConfiguration( folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, @@ -76,6 +78,18 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi ); } } + + 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); + } + protected async pickDebugConfiguration( input: IMultiStepInput, state: DebugConfigurationState diff --git a/src/client/debugger/extension/configuration/resolvers/attach.ts b/src/client/debugger/extension/configuration/resolvers/attach.ts index bb8fdf913bed..2a641502d865 100644 --- a/src/client/debugger/extension/configuration/resolvers/attach.ts +++ b/src/client/debugger/extension/configuration/resolvers/attach.ts @@ -21,7 +21,8 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver implements IDebugConfigurationResolver { protected pythonPathSource: PythonPathSource = PythonPathSource.launchJson; + constructor( protected readonly workspaceService: IWorkspaceService, protected readonly documentManager: IDocumentManager, protected readonly platformService: IPlatformService, protected readonly configurationService: IConfigurationService - ) {} - public abstract resolveDebugConfiguration( + ) { } + + // This is a legacy hook used solely for backwards-compatible manual substitution + // of ${command:python.interpreterPath} in "pythonPath". Newly added debug variables + // should be properly registered in package.json, so that they're automatically + // substituted by VSCode itself. All other validation in derived classes should be + // performed in resolveDebugConfigurationWithSubstitutedVariables() instead, where + // all variables are already substituted. + public async resolveDebugConfiguration( + _folder: WorkspaceFolder | undefined, + debugConfiguration: DebugConfiguration, + _token?: CancellationToken + ): Promise { + return debugConfiguration as T; + } + + public abstract resolveDebugConfigurationWithSubstitutedVariables( folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken ): Promise; + protected getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { if (folder) { return folder.uri; @@ -56,12 +73,14 @@ export abstract class BaseConfigurationResolver } } } + protected getProgram(): string | undefined { const editor = this.documentManager.activeTextEditor; if (editor && editor.document.languageId === PYTHON_LANGUAGE) { return editor.document.fileName; } } + protected resolveAndUpdatePaths( workspaceFolder: Uri | undefined, debugConfiguration: LaunchRequestArguments @@ -69,6 +88,7 @@ export abstract class BaseConfigurationResolver this.resolveAndUpdateEnvFilePath(workspaceFolder, debugConfiguration); this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); } + protected resolveAndUpdateEnvFilePath( workspaceFolder: Uri | undefined, debugConfiguration: LaunchRequestArguments @@ -84,6 +104,7 @@ export abstract class BaseConfigurationResolver debugConfiguration.envFile = systemVariables.resolveAny(debugConfiguration.envFile); } } + protected resolveAndUpdatePythonPath( workspaceFolder: Uri | undefined, debugConfiguration: LaunchRequestArguments @@ -99,16 +120,19 @@ export abstract class BaseConfigurationResolver this.pythonPathSource = PythonPathSource.launchJson; } } + protected debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { if (debugOptions.indexOf(debugOption) >= 0) { return; } debugOptions.push(debugOption); } + protected isLocalHost(hostName?: string) { const LocalHosts = ['localhost', '127.0.0.1', '::1']; return hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0 ? true : false; } + protected fixUpPathMappings( pathMappings: PathMapping[], defaultLocalRoot?: string, @@ -153,9 +177,11 @@ export abstract class BaseConfigurationResolver return pathMappings; } + protected isDebuggingFlask(debugConfiguration: Partial) { return debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' ? true : false; } + protected sendTelemetry( trigger: 'launch' | 'attach' | 'test', debugConfiguration: Partial diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 9d78188207df..6c5ae287bd62 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -29,47 +29,69 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { - const workspaceFolder = this.getWorkspaceFolder(folder); - - const config = debugConfiguration as LaunchRequestArguments; - const numberOfSettings = Object.keys(config); - - if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { + if ( + debugConfiguration.name === undefined && + debugConfiguration.type === undefined && + debugConfiguration.request === undefined && + debugConfiguration.program === undefined && + debugConfiguration.env === undefined + ) { const defaultProgram = this.getProgram(); - - config.name = 'Launch'; - config.type = DebuggerTypeName; - config.request = 'launch'; - config.program = defaultProgram ? defaultProgram : ''; - config.env = {}; + debugConfiguration.name = 'Launch'; + debugConfiguration.type = DebuggerTypeName; + debugConfiguration.request = 'launch'; + debugConfiguration.program = defaultProgram ?? ''; + debugConfiguration.env = {}; } - await this.provideLaunchDefaults(workspaceFolder, config); + const workspaceFolder = this.getWorkspaceFolder(folder); + this.resolveAndUpdatePaths(workspaceFolder, debugConfiguration); + return debugConfiguration; + } - const isValid = await this.validateLaunchConfiguration(folder, config); + public async resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: LaunchRequestArguments, + _token?: CancellationToken + ): Promise { + const workspaceFolder = this.getWorkspaceFolder(folder); + await this.provideLaunchDefaults(workspaceFolder, debugConfiguration); + + const isValid = await this.validateLaunchConfiguration(folder, debugConfiguration); if (!isValid) { return; } - const dbgConfig = debugConfiguration; - if (Array.isArray(dbgConfig.debugOptions)) { - dbgConfig.debugOptions = dbgConfig.debugOptions!.filter( - (item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos + if (Array.isArray(debugConfiguration.debugOptions)) { + debugConfiguration.debugOptions = debugConfiguration.debugOptions!.filter( + (item, pos) => debugConfiguration.debugOptions!.indexOf(item) === pos ); } return debugConfiguration; } + // tslint:disable-next-line:cyclomatic-complexity protected async provideLaunchDefaults( workspaceFolder: Uri | undefined, debugConfiguration: LaunchRequestArguments ): Promise { - this.resolveAndUpdatePaths(workspaceFolder, debugConfiguration); + if (debugConfiguration.python === undefined) { + debugConfiguration.python = debugConfiguration.pythonPath; + } + if (debugConfiguration.debugAdapterPython === undefined) { + debugConfiguration.debugAdapterPython = debugConfiguration.pythonPath; + } + if (debugConfiguration.debugLauncherPython === undefined) { + debugConfiguration.debugLauncherPython = debugConfiguration.pythonPath; + } + delete debugConfiguration.pythonPath; + if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; } @@ -160,10 +182,19 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { const diagnosticService = this.invalidPythonPathInDebuggerService; - return diagnosticService.validatePythonPath( - debugConfiguration.pythonPath, - this.pythonPathSource, - folder ? folder.uri : undefined + const resource = folder ? folder.uri : undefined; + return ( + diagnosticService.validatePythonPath(debugConfiguration.python, this.pythonPathSource, resource) && + diagnosticService.validatePythonPath( + debugConfiguration.debugAdapterPython, + this.pythonPathSource, + resource + ) && + diagnosticService.validatePythonPath( + debugConfiguration.debugLauncherPython, + this.pythonPathSource, + resource + ) ); } } diff --git a/src/client/debugger/extension/configuration/types.ts b/src/client/debugger/extension/configuration/types.ts index 1331ea39551a..947074f08292 100644 --- a/src/client/debugger/extension/configuration/types.ts +++ b/src/client/debugger/extension/configuration/types.ts @@ -13,6 +13,12 @@ export interface IDebugConfigurationResolver { debugConfiguration: T, token?: CancellationToken ): Promise; + + resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: T, + token?: CancellationToken + ): Promise; } export const IDebugConfigurationProviderFactory = Symbol('IDebugConfigurationProviderFactory'); diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 68ad0e49a149..c6fa10df4c4c 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -70,37 +70,41 @@ export interface IKnownLaunchRequestArguments extends ICommonDebugArguments { // An absolute path to the program to debug. module?: string; program?: string; - pythonPath: string; + pythonPath?: string; + python?: string; // Automatically stop target after launch. If not specified, target does not stop. stopOnEntry?: boolean; - args: string[]; + args?: string[]; cwd?: string; debugOptions?: DebugOptions[]; env?: Record; - envFile: string; + envFile?: string; console?: ConsoleType; // Internal field used to set custom python debug adapter (for testing) debugAdapterPath?: string; + debugAdapterPython?: string; + debugLauncherPython?: string; } + // tslint:disable-next-line:interface-name export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments, - IKnownLaunchRequestArguments, - DebugConfiguration { + IKnownLaunchRequestArguments, + DebugConfiguration { type: typeof DebuggerTypeName; } // tslint:disable-next-line:interface-name export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments, - IKnownAttachDebugArguments, - DebugConfiguration { + IKnownAttachDebugArguments, + DebugConfiguration { type: typeof DebuggerTypeName; } // tslint:disable-next-line:interface-name -export interface DebugConfigurationArguments extends LaunchRequestArguments, AttachRequestArguments {} +export interface DebugConfigurationArguments extends LaunchRequestArguments, AttachRequestArguments { } export type ConsoleType = 'internalConsole' | 'integratedTerminal' | 'externalTerminal'; diff --git a/src/client/testing/common/debugLauncher.ts b/src/client/testing/common/debugLauncher.ts index 969c02e19eb7..23e475313833 100644 --- a/src/client/testing/common/debugLauncher.ts +++ b/src/client/testing/common/debugLauncher.ts @@ -164,7 +164,7 @@ export class DebugLauncher implements ITestDebugLauncher { configArgs.args = args.slice(1); // We leave configArgs.request as "test" so it will be sent in telemetry. - const launchArgs = await this.launchResolver.resolveDebugConfiguration( + let launchArgs = await this.launchResolver.resolveDebugConfiguration( workspaceFolder, configArgs, options.token @@ -172,9 +172,17 @@ export class DebugLauncher implements ITestDebugLauncher { if (!launchArgs) { throw Error(`Invalid debug config "${debugConfig.name}"`); } + launchArgs = await this.launchResolver.resolveDebugConfigurationWithSubstitutedVariables( + workspaceFolder, + launchArgs, + options.token + ); + if (!launchArgs) { + throw Error(`Invalid debug config "${debugConfig.name}"`); + } launchArgs.request = 'launch'; - return launchArgs!; + return launchArgs; } private fixArgs(args: string[], testProvider: TestProvider): string[] { diff --git a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts index 474e8bf55601..0d3355623467 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -45,6 +45,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { let configurationService: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; const debugOptionsAvailable = getAvailableOptions(); + setup(() => { serviceContainer = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); @@ -64,11 +65,13 @@ getInfoPerOS().forEach(([osName, osType, path]) => { configurationService.object ); }); + function createMoqWorkspaceFolder(folderPath: string) { const folder = TypeMoq.Mock.ofType(); folder.setup((f) => f.uri).returns(() => Uri.file(folderPath)); return folder.object; } + function setupActiveEditor(fileName: string | undefined, languageId: string) { if (fileName) { const textEditor = TypeMoq.Mock.ofType(); @@ -84,6 +87,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { .setup((c) => c.get(TypeMoq.It.isValue(IDocumentManager))) .returns(() => documentManager.object); } + function setupWorkspaces(folders: string[]) { const workspaceFolders = folders.map(createMoqWorkspaceFolder); workspaceService.setup((w) => w.workspaceFolders).returns(() => workspaceFolders); @@ -91,57 +95,87 @@ getInfoPerOS().forEach(([osName, osType, path]) => { .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) .returns(() => workspaceService.object); } + + const attach: Partial = { + name: 'Python attach', + type: 'python', + request: 'attach' + }; + + async function resolveDebugConfiguration( + workspaceFolder: WorkspaceFolder | undefined, + attachConfig: Partial + ) { + let config = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + attachConfig as DebugConfiguration + ); + if (config === undefined || config === null) { + return config; + } + + config = await debugProvider.resolveDebugConfigurationWithSubstitutedVariables!(workspaceFolder, config); + if (config === undefined || config === null) { + return config; + } + + return config as AttachRequestArguments; + } + test('Defaults should be returned when an empty object is passed with a Workspace Folder and active file', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { request: 'attach' - } as DebugConfiguration); + }); expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); expect(debugConfig).to.have.property('request', 'attach'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { const pythonFile = 'xyz.py'; setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { + const debugConfig = await resolveDebugConfiguration(undefined, { request: 'attach' - } as DebugConfiguration); + }); expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.have.property('host', 'localhost'); }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { setupActiveEditor(undefined, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { + const debugConfig = await resolveDebugConfiguration(undefined, { request: 'attach' - } as DebugConfiguration); + }); expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.have.property('host', 'localhost'); }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { const activeFile = 'xyz.js'; setupActiveEditor(activeFile, 'javascript'); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { + const debugConfig = await resolveDebugConfiguration(undefined, { request: 'attach' - } as DebugConfiguration); + }); expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); @@ -149,47 +183,51 @@ getInfoPerOS().forEach(([osName, osType, path]) => { expect(debugConfig).to.not.have.property('localRoot'); expect(debugConfig).to.have.property('host', 'localhost'); }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { const activeFile = 'xyz.py'; setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { + const debugConfig = await resolveDebugConfiguration(undefined, { request: 'attach' - } as DebugConfiguration); + }); expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.have.property('host', 'localhost'); }); + test('Default host should not be added if connect is available.', async () => { const pythonFile = 'xyz.py'; setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { - request: 'attach', + const debugConfig = await resolveDebugConfiguration(undefined, { + ...attach, connect: { host: 'localhost', port: 5678 } - } as AttachRequestArguments); + }); expect(debugConfig).to.not.have.property('host', 'localhost'); }); + test('Default host should not be added if listen is available.', async () => { const pythonFile = 'xyz.py'; setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { - request: 'attach', + const debugConfig = await resolveDebugConfiguration(undefined, { + ...attach, listen: { host: 'localhost', port: 5678 } } as AttachRequestArguments); expect(debugConfig).to.not.have.property('host', 'localhost'); }); + test("Ensure 'localRoot' is left unaltered", async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -198,13 +236,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - localRoot, - request: 'attach' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + localRoot + }); expect(debugConfig).to.have.property('localRoot', localRoot); }); + ['localhost', 'LOCALHOST', '127.0.0.1', '::1'].forEach((host) => { test(`Ensure path mappings are automatically added when host is '${host}'`, async () => { const activeFile = 'xyz.py'; @@ -214,11 +253,11 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); expect(debugConfig).to.have.property('localRoot', localRoot); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; @@ -226,6 +265,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { expect(pathMappings![0].localRoot).to.be.equal(workspaceFolder.uri.fsPath); expect(pathMappings![0].remoteRoot).to.be.equal(workspaceFolder.uri.fsPath); }); + test(`Ensure drive letter is lower cased for local path mappings on Windows when host is '${host}'`, async function () { if (getOSType() !== OSType.Windows || osType !== OSType.Windows) { return this.skip(); @@ -237,17 +277,18 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = Uri.file(path.join('c:', 'Debug', 'Python_Path')).fsPath; expect(pathMappings![0].localRoot).to.be.equal(expected); expect(pathMappings![0].remoteRoot).to.be.equal(workspaceFolder.uri.fsPath); }); + test(`Ensure drive letter is not lower cased for local path mappings on non-Windows when host is '${host}'`, async function () { if (getOSType() === OSType.Windows || osType === OSType.Windows) { return this.skip(); @@ -259,17 +300,18 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = Uri.file(path.join('USR', 'Debug', 'Python_Path')).fsPath; expect(pathMappings![0].localRoot).to.be.equal(expected); expect(pathMappings![0].remoteRoot).to.be.equal(workspaceFolder.uri.fsPath); }); + test(`Ensure drive letter is lower cased for local path mappings on Windows when host is '${host}' and with existing path mappings`, async function () { if (getOSType() !== OSType.Windows || osType !== OSType.Windows) { return this.skip(); @@ -284,18 +326,19 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const debugPathMappings = [ { localRoot: path.join('${workspaceFolder}', localRoot), remoteRoot: '/app/' } ]; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, pathMappings: debugPathMappings, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = Uri.file(path.join('c:', 'Debug', 'Python_Path', localRoot)).fsPath; expect(pathMappings![0].localRoot).to.be.equal(expected); expect(pathMappings![0].remoteRoot).to.be.equal('/app/'); }); + test(`Ensure drive letter is not lower cased for local path mappings on non-Windows when host is '${host}' and with existing path mappings`, async function () { if (getOSType() === OSType.Windows || osType === OSType.Windows) { return this.skip(); @@ -310,18 +353,19 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const debugPathMappings = [ { localRoot: path.join('${workspaceFolder}', localRoot), remoteRoot: '/app/' } ]; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, pathMappings: debugPathMappings, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = Uri.file(path.join('USR', 'Debug', 'Python_Path', localRoot)).fsPath; expect(pathMappings![0].localRoot).to.be.equal(expected); expect(pathMappings![0].remoteRoot).to.be.equal('/app/'); }); + test(`Ensure local path mappings are not modified when not pointing to a local drive when host is '${host}'`, async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(path.join('Server', 'Debug', 'Python_Path')); @@ -330,17 +374,18 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; expect(pathMappings![0].localRoot).to.be.equal(workspaceFolder.uri.fsPath); expect(pathMappings![0].remoteRoot).to.be.equal(workspaceFolder.uri.fsPath); }); }); + ['192.168.1.123', 'don.debugger.com'].forEach((host) => { test(`Ensure path mappings are not automatically added when host is '${host}'`, async () => { const activeFile = 'xyz.py'; @@ -350,17 +395,18 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - host, - request: 'attach' - } as any) as DebugConfiguration); + host + }); expect(debugConfig).to.have.property('localRoot', localRoot); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; expect(pathMappings || []).to.be.lengthOf(0); }); }); + test("Ensure 'localRoot' and 'remoteRoot' is used", async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -370,15 +416,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - remoteRoot, - request: 'attach' - } as any) as DebugConfiguration); + remoteRoot + }); expect(debugConfig!.pathMappings).to.be.lengthOf(1); expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); }); + test("Ensure 'localRoot' and 'remoteRoot' is used", async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -388,15 +435,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, localRoot, - remoteRoot, - request: 'attach' - } as any) as DebugConfiguration); + remoteRoot + }); expect(debugConfig!.pathMappings).to.be.lengthOf(1); expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); }); + test("Ensure 'remoteRoot' is left unaltered", async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -405,13 +453,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - remoteRoot, - request: 'attach' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + remoteRoot + }); expect(debugConfig).to.have.property('remoteRoot', remoteRoot); }); + test("Ensure 'port' is left unaltered", async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -420,10 +469,10 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const port = 12341234; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - port, - request: 'attach' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + port + }); expect(debugConfig).to.have.property('port', port); }); @@ -434,12 +483,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const debugOptions = debugOptionsAvailable + .slice() + .concat(DebugOptions.Jinja, DebugOptions.Sudo) as DebugOptions[]; const expectedDebugOptions = debugOptions.slice(); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - debugOptions, - request: 'attach' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + debugOptions + }); expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(expectedDebugOptions); }); @@ -498,15 +549,17 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const debugOptions = debugOptionsAvailable + .slice() + .concat(DebugOptions.Jinja, DebugOptions.Sudo) as DebugOptions[]; testsForJustMyCode.forEach(async (testParams) => { - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, debugOptions, - request: 'attach', justMyCode: testParams.justMyCode, debugStdLib: testParams.debugStdLib - } as any) as DebugConfiguration); + }); expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); }); }); diff --git a/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts b/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts index 1c8854e5a8bb..30f8f6eeb344 100644 --- a/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts @@ -31,24 +31,38 @@ suite('Debugging - Config Resolver', () => { ): Promise { throw new Error('Not Implemented'); } + + public resolveDebugConfigurationWithSubstitutedVariables( + _folder: WorkspaceFolder | undefined, + _debugConfiguration: DebugConfiguration, + _token?: CancellationToken + ): Promise { + throw new Error('Not Implemented'); + } + public getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { return super.getWorkspaceFolder(folder); } + public getProgram(): string | undefined { return super.getProgram(); } + public resolveAndUpdatePythonPath( workspaceFolder: Uri | undefined, debugConfiguration: LaunchRequestArguments ): void { return super.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); } + public debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { return super.debugOption(debugOptions, debugOption); } + public isLocalHost(hostName?: string) { return super.isLocalHost(hostName); } + public isDebuggingFlask(debugConfiguration: Partial) { return super.isDebuggingFlask(debugConfiguration); } @@ -185,7 +199,7 @@ suite('Debugging - Config Resolver', () => { test('Do nothing if debug configuration is undefined', () => { resolver.resolveAndUpdatePythonPath(undefined, undefined as any); }); - test('Python path in debug config must point to pythonpath in settings if pythonPath in config is not set', () => { + test('pythonPath in debug config must point to pythonPath in settings if pythonPath in config is not set', () => { const config = {}; const pythonPath = path.join('1', '2', '3'); @@ -195,7 +209,7 @@ suite('Debugging - Config Resolver', () => { expect(config).to.have.property('pythonPath', pythonPath); }); - test('Python path in debug config must point to pythonpath in settings if pythonPath in config is ${command:python.interpreterPath}', () => { + test('pythonPath in debug config must point to pythonPath in settings if pythonPath in config is ${command:python.interpreterPath}', () => { const config = { pythonPath: '${command:python.interpreterPath}' }; diff --git a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts index ce0796459b32..0bf10c6c3d11 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -37,11 +37,13 @@ getInfoPerOS().forEach(([osName, osType, path]) => { let documentManager: TypeMoq.IMock; let diagnosticsService: TypeMoq.IMock; let debugEnvHelper: TypeMoq.IMock; + function createMoqWorkspaceFolder(folderPath: string) { const folder = TypeMoq.Mock.ofType(); folder.setup((f) => f.uri).returns(() => Uri.file(folderPath)); return folder.object; } + function setupIoc(pythonPath: string, workspaceFolder?: WorkspaceFolder) { const configService = TypeMoq.Mock.ofType(); workspaceService = TypeMoq.Mock.ofType(); @@ -83,6 +85,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { debugEnvHelper.object ); } + function setupActiveEditor(fileName: string | undefined, languageId: string) { if (fileName) { const textEditor = TypeMoq.Mock.ofType(); @@ -95,27 +98,54 @@ getInfoPerOS().forEach(([osName, osType, path]) => { documentManager.setup((d) => d.activeTextEditor).returns(() => undefined); } } + function setupWorkspaces(folders: string[]) { const workspaceFolders = folders.map(createMoqWorkspaceFolder); workspaceService.setup((w) => w.workspaceFolders).returns(() => workspaceFolders); } + + const launch: LaunchRequestArguments = { + name: 'Python launch', + type: 'python', + request: 'launch' + }; + + async function resolveDebugConfiguration( + workspaceFolder: WorkspaceFolder | undefined, + launchConfig: Partial + ) { + let config = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + launchConfig as DebugConfiguration + ); + if (config === undefined || config === null) { + return config; + } + + config = await debugProvider.resolveDebugConfigurationWithSubstitutedVariables!(workspaceFolder, config); + if (config === undefined || config === null) { + return config; + } + + return config as LaunchRequestArguments; + } + 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'; setupIoc(pythonPath, workspaceFolder); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!( - workspaceFolder, - {} as DebugConfiguration - ); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, {}); expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); - expect(debugConfig).to.have.property('pythonPath', pythonPath); expect(debugConfig).to.have.property('type', 'python'); expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('program', pythonFile); expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); @@ -125,6 +155,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // 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); @@ -132,14 +163,17 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath, workspaceFolder); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await 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', 'python'); expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('program', pythonFile); expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); @@ -149,6 +183,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // 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'; @@ -156,13 +191,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(undefined, {}); const filePath = Uri.file(path.dirname('')).fsPath; expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); - expect(debugConfig).to.have.property('pythonPath', pythonPath); expect(debugConfig).to.have.property('type', 'python'); expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('program', pythonFile); expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); @@ -172,17 +210,21 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // 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()}`; setupIoc(pythonPath); setupActiveEditor(undefined, PYTHON_LANGUAGE); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(undefined, {}); expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); - expect(debugConfig).to.have.property('pythonPath', pythonPath); expect(debugConfig).to.have.property('type', 'python'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('request', 'launch'); expect(debugConfig).to.have.property('program', ''); expect(debugConfig).not.to.have.property('cwd'); @@ -191,6 +233,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // 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'; @@ -198,12 +241,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupActiveEditor(activeFile, 'javascript'); setupWorkspaces([]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(undefined, {}); expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); - expect(debugConfig).to.have.property('pythonPath', pythonPath); expect(debugConfig).to.have.property('type', 'python'); expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('program', ''); expect(debugConfig).not.to.have.property('cwd'); expect(debugConfig).not.to.have.property('envFile'); @@ -211,6 +257,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // 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'; @@ -219,13 +266,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupActiveEditor(activeFile, PYTHON_LANGUAGE); setupWorkspaces([defaultWorkspace]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(undefined, {}); const filePath = Uri.file(defaultWorkspace).fsPath; expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); - expect(debugConfig).to.have.property('pythonPath', pythonPath); expect(debugConfig).to.have.property('type', 'python'); expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); expect(debugConfig).to.have.property('program', activeFile); expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); @@ -235,6 +285,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { // tslint:disable-next-line:no-any expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); }); + test("Ensure 'port' is left unaltered", async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -242,13 +293,13 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const port = 12341234; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - port, - request: 'launch' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + port + }); expect(debugConfig).to.have.property('port', port); }); + test("Ensure 'localRoot' is left unaltered", async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -256,13 +307,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - localRoot, - request: 'launch' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + localRoot + }); expect(debugConfig).to.have.property('localRoot', localRoot); }); + test("Ensure 'remoteRoot' is left unaltered", async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -270,13 +322,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - remoteRoot, - request: 'launch' - } as any) as DebugConfiguration); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + remoteRoot + }); expect(debugConfig).to.have.property('remoteRoot', remoteRoot); }); + test("Ensure 'localRoot' and 'remoteRoot' are not used", async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -285,14 +338,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, localRoot, - remoteRoot, - request: 'launch' - } as any) as DebugConfiguration); + remoteRoot + }); expect(debugConfig!.pathMappings).to.be.equal(undefined, 'unexpected pathMappings'); }); + test('Ensure non-empty path mappings are used', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -303,29 +357,30 @@ getInfoPerOS().forEach(([osName, osType, path]) => { localRoot: `Debug_PythonPath_Local_Root_${new Date().toString()}`, remoteRoot: `Debug_PythonPath_Remote_Root_${new Date().toString()}` }; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pathMappings: [expected] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.be.deep.equal([expected]); }); + test('Ensure replacement in path mappings happens', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pathMappings: [ { localRoot: '${workspaceFolder}/spam', remoteRoot: '${workspaceFolder}/spam' } ] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.be.deep.equal([ @@ -335,6 +390,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } ]); }); + test('Ensure path mappings are not automatically added if missing', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -342,14 +398,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, localRoot: localRoot - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.be.equal(undefined, 'unexpected pathMappings'); }); + test('Ensure path mappings are not automatically added if empty', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -357,15 +414,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, localRoot: localRoot, pathMappings: [] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.be.equal(undefined, 'unexpected pathMappings'); }); + test('Ensure path mappings are not automatically added to existing', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); @@ -373,8 +431,8 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, localRoot: localRoot, pathMappings: [ { @@ -382,7 +440,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { remoteRoot: '.' } ] - } as any) as DebugConfiguration); + }); expect(debugConfig).to.have.property('localRoot', localRoot); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; @@ -393,6 +451,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } ]); }); + test('Ensure drive letter is lower cased for local path mappings on Windows when with existing path mappings', async function () { if (getOSType() !== OSType.Windows || osType !== OSType.Windows) { // tslint:disable-next-line: no-invalid-this @@ -404,15 +463,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = Uri.file(path.join(workspaceFolder.uri.fsPath, 'app')).fsPath; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pathMappings: [ { localRoot, remoteRoot: '/app/' } ] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; const expected = Uri.file(`c${localRoot.substring(1)}`).fsPath; @@ -423,6 +482,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } ]); }); + test('Ensure drive letter is not lower cased for local path mappings on non-Windows when with existing path mappings', async function () { if (getOSType() === OSType.Windows || osType === OSType.Windows) { // tslint:disable-next-line: no-invalid-this @@ -434,15 +494,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const localRoot = Uri.file(path.join(workspaceFolder.uri.fsPath, 'app')).fsPath; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pathMappings: [ { localRoot, remoteRoot: '/app/' } ] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.deep.equal([ @@ -452,21 +512,22 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } ]); }); + test('Ensure local path mappings are not modified when not pointing to a local drive', async () => { const workspaceFolder = createMoqWorkspaceFolder(path.join('Server', 'Debug', 'Python_Path')); setupActiveEditor('spam.py', PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ - request: 'launch', + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pathMappings: [ { localRoot: '/spam', remoteRoot: '.' } ] - } as any) as DebugConfiguration); + }); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; expect(pathMappings).to.deep.equal([ @@ -476,6 +537,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } ]); }); + test('Ensure `${command:python.interpreterPath}` is replaced with actual pythonPath', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const activeFile = 'xyz.py'; @@ -485,12 +547,17 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pythonPath: '${command:python.interpreterPath}' - } as any) as DebugConfiguration); + }); - expect(debugConfig).to.have.property('pythonPath', pythonPath); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', pythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', pythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', pythonPath); }); + test('Ensure hardcoded pythonPath is left unaltered', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const activeFile = 'xyz.py'; @@ -501,12 +568,17 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupWorkspaces([defaultWorkspace]); const debugPythonPath = `Debug_PythonPath_${new Date().toString()}`; - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, pythonPath: debugPythonPath - } as any) as DebugConfiguration); + }); - expect(debugConfig).to.have.property('pythonPath', debugPythonPath); + expect(debugConfig).to.not.have.property('pythonPath'); + expect(debugConfig).to.have.property('python', debugPythonPath); + expect(debugConfig).to.have.property('debugAdapterPython', debugPythonPath); + expect(debugConfig).to.have.property('debugLauncherPython', debugPythonPath); }); + test('Test defaults of debugger', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -514,10 +586,9 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!( - workspaceFolder, - {} as DebugConfiguration - ); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch + }); expect(debugConfig).to.have.property('console', 'integratedTerminal'); expect(debugConfig).to.have.property('stopOnEntry', false); @@ -529,6 +600,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } expect((debugConfig as any).debugOptions).to.be.deep.equal(expectedOptions); }); + test('Test defaults of python debugger', async () => { if ('python' === DebuggerTypeName) { return; @@ -539,16 +611,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!( - workspaceFolder, - {} as DebugConfiguration - ); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch + }); expect(debugConfig).to.have.property('stopOnEntry', false); expect(debugConfig).to.have.property('showReturnValue', true); expect(debugConfig).to.have.property('debugOptions'); expect((debugConfig as any).debugOptions).to.be.deep.equal([]); }); + test('Test overriding defaults of debugger', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -556,10 +628,11 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, redirectOutput: true, justMyCode: false - } as LaunchRequestArguments); + }); expect(debugConfig).to.have.property('console', 'integratedTerminal'); expect(debugConfig).to.have.property('stopOnEntry', false); @@ -577,6 +650,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } expect((debugConfig as any).debugOptions).to.be.deep.equal(expectedOptions); }); + const testsForJustMyCode = [ { justMyCode: false, @@ -631,13 +705,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); testsForJustMyCode.forEach(async (testParams) => { - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, debugStdLib: testParams.debugStdLib, justMyCode: testParams.justMyCode - } as LaunchRequestArguments); + }); expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); }); }); + const testsForRedirectOutput = [ { console: 'internalConsole', @@ -692,10 +768,11 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); testsForRedirectOutput.forEach(async (testParams) => { - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { - console: testParams.console, + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + console: testParams.console as any, redirectOutput: testParams.redirectOutput - } as LaunchRequestArguments); + }); expect(debugConfig).to.have.property('redirectOutput', testParams.expectedRedirectOutput); if (testParams.expectedRedirectOutput) { expect(debugConfig).to.have.property('debugOptions'); @@ -703,6 +780,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { } }); }); + test('Test fixFilePathCase', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -710,16 +788,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!( - workspaceFolder, - {} as DebugConfiguration - ); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch + }); if (osType === OSType.Windows) { expect(debugConfig).to.have.property('debugOptions').contains(DebugOptions.FixFilePathCase); } else { expect(debugConfig).to.have.property('debugOptions').not.contains(DebugOptions.FixFilePathCase); } }); + test('Jinja added for Pyramid', async () => { const workspacePath = path.join('usr', 'development', 'wksp1'); const pythonPath = path.join(workspacePath, 'env', 'bin', 'python'); @@ -729,15 +807,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const options = { debugOptions: [DebugOptions.Pyramid], pyramid: true }; + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + debugOptions: [DebugOptions.Pyramid], + pyramid: true + }); - const debugConfig = await debugProvider.resolveDebugConfiguration!( - workspaceFolder, - (options as any) as DebugConfiguration - ); expect(debugConfig).to.have.property('debugOptions'); expect((debugConfig as any).debugOptions).contains(DebugOptions.Jinja); }); + test('Auto detect flask debugging', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -745,13 +824,15 @@ getInfoPerOS().forEach(([osName, osType, path]) => { setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, ({ + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, module: 'flask' - } as any) as DebugConfiguration); + }); expect(debugConfig).to.have.property('debugOptions'); expect((debugConfig as any).debugOptions).contains(DebugOptions.Jinja); }); + test('Test validation of Python Path when launching debugger (with invalid python path)', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -765,16 +846,18 @@ getInfoPerOS().forEach(([osName, osType, path]) => { h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny()) ) .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); + .verifiable(TypeMoq.Times.atLeastOnce()); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, redirectOutput: false, pythonPath - } as LaunchRequestArguments); + }); diagnosticsService.verifyAll(); expect(debugConfig).to.be.equal(undefined, 'Not undefined'); }); + test('Test validation of Python Path when launching debugger (with valid python path)', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -788,23 +871,24 @@ getInfoPerOS().forEach(([osName, osType, path]) => { h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny()) ) .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); + .verifiable(TypeMoq.Times.atLeastOnce()); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, redirectOutput: false, pythonPath - } as LaunchRequestArguments); + }); diagnosticsService.verifyAll(); expect(debugConfig).to.not.be.equal(undefined, 'is undefined'); }); + test('Resolve path to envFile', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; - const expectedEnvFilePath = `${workspaceFolder.uri.fsPath}${ - osType === OSType.Windows ? '\\' : '/' - }${'wow.envFile'}`; + const sep = osType === OSType.Windows ? '\\' : '/'; + const expectedEnvFilePath = `${workspaceFolder.uri.fsPath}${sep}${'wow.envFile'}`; setupIoc(pythonPath); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); @@ -815,14 +899,16 @@ getInfoPerOS().forEach(([osName, osType, path]) => { ) .returns(() => Promise.resolve(true)); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, redirectOutput: false, pythonPath, envFile: path.join('${workspaceFolder}', 'wow.envFile') - } as LaunchRequestArguments); + }); expect(debugConfig!.envFile).to.be.equal(expectedEnvFilePath); }); + async function testSetting( requestType: 'launch' | 'attach', settings: Record, @@ -830,7 +916,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { mustHaveDebugOption: boolean ) { setupIoc('pythonPath'); - const debugConfiguration: DebugConfiguration = { + let debugConfig: DebugConfiguration = { request: requestType, type: 'python', name: '', @@ -838,23 +924,28 @@ getInfoPerOS().forEach(([osName, osType, path]) => { }; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, debugConfiguration); + debugConfig = (await debugProvider.resolveDebugConfiguration!(workspaceFolder, debugConfig))!; + debugConfig = (await debugProvider.resolveDebugConfigurationWithSubstitutedVariables!( + workspaceFolder, + debugConfig + ))!; + if (mustHaveDebugOption) { - expect((debugConfig as any).debugOptions).contains(debugOptionName); + expect(debugConfig.debugOptions).contains(debugOptionName); } else { - expect((debugConfig as any).debugOptions).not.contains(debugOptionName); + expect(debugConfig.debugOptions).not.contains(debugOptionName); } } type LaunchOrAttach = 'launch' | 'attach'; const items: LaunchOrAttach[] = ['launch', 'attach']; items.forEach((requestType) => { - test(`Must not contain Sub Process when not specified (${requestType})`, async () => { + test(`Must not contain Sub Process when not specified(${requestType})`, async () => { await testSetting(requestType, {}, DebugOptions.SubProcess, false); }); - test(`Must not contain Sub Process setting=false (${requestType})`, async () => { + test(`Must not contain Sub Process setting = false(${requestType})`, async () => { await testSetting(requestType, { subProcess: false }, DebugOptions.SubProcess, false); }); - test(`Must not contain Sub Process setting=true (${requestType})`, async () => { + test(`Must not contain Sub Process setting = true(${requestType})`, async () => { await testSetting(requestType, { subProcess: true }, DebugOptions.SubProcess, true); }); }); diff --git a/src/test/testing/common/debugLauncher.unit.test.ts b/src/test/testing/common/debugLauncher.unit.test.ts index c1782b7c8027..0abe2ce24bdb 100644 --- a/src/test/testing/common/debugLauncher.unit.test.ts +++ b/src/test/testing/common/debugLauncher.unit.test.ts @@ -119,7 +119,6 @@ suite('Unit Tests - Debug Launcher', () => { .setup((d) => d.getEnvironmentVariables(TypeMoq.It.isAny())) .returns(() => Promise.resolve(expected.env)); - //debugService.setup(d => d.startDebugging(TypeMoq.It.isValue(workspaceFolder), TypeMoq.It.isValue(expected))) debugService .setup((d) => d.startDebugging(TypeMoq.It.isValue(workspaceFolder), TypeMoq.It.isValue(expected))) .returns((_wspc: WorkspaceFolder, _expectedParam: DebugConfiguration) => { @@ -207,8 +206,14 @@ suite('Unit Tests - Debug Launcher', () => { } // added by LaunchConfigurationResolver: - if (!expected.pythonPath) { - expected.pythonPath = 'python'; + if (!expected.python) { + expected.python = 'python'; + } + if (!expected.debugAdapterPython) { + expected.debugAdapterPython = 'python'; + } + if (!expected.debugLauncherPython) { + expected.debugLauncherPython = 'python'; } expected.workspaceFolder = workspaceFolders[0].uri.fsPath; expected.debugOptions = []; @@ -324,7 +329,9 @@ suite('Unit Tests - Debug Launcher', () => { name: 'my tests', type: DebuggerTypeName, request: 'launch', - pythonPath: 'some/dir/bin/py3', + python: 'some/dir/bin/py3', + debugAdapterPython: 'some/dir/bin/py3', + debugLauncherPython: 'some/dir/bin/py3', stopOnEntry: true, showReturnValue: true, console: 'integratedTerminal', @@ -345,7 +352,7 @@ suite('Unit Tests - Debug Launcher', () => { name: 'my tests', type: DebuggerTypeName, request: 'test', - pythonPath: expected.pythonPath, + pythonPath: expected.python, stopOnEntry: expected.stopOnEntry, showReturnValue: expected.showReturnValue, console: expected.console,