From df3240c2fefbfa90b0b0e41d3c97b7ca93edd3dc Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 12:39:48 -0600 Subject: [PATCH 01/22] Allow "pathMappings" in "launch" debug configs. --- package.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/package.json b/package.json index c022da8be5d3..fc32c53cf181 100644 --- a/package.json +++ b/package.json @@ -1024,6 +1024,31 @@ "description": "IP address of the of the local debug server (default is localhost).", "default": "localhost" }, + "pathMappings": { + "type": "array", + "label": "Path mappings.", + "items": { + "type": "object", + "label": "Path mapping", + "required": [ + "localRoot", + "remoteRoot" + ], + "properties": { + "localRoot": { + "type": "string", + "label": "Local source root.", + "default": "${workspaceFolder}" + }, + "remoteRoot": { + "type": "string", + "label": "Remote source root.", + "default": "" + } + } + }, + "default": [] + }, "logToFile": { "type": "boolean", "description": "Enable logging of debugger events to a log file.", From 31563a1940ae3dd93169bf85c1b3ab037b089677 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 12:55:32 -0600 Subject: [PATCH 02/22] Add "pathMappings" to IKnownLaunchRequestArguments. --- src/client/debugger/types.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 48beb568c3a2..3792bc6e6db1 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -36,14 +36,15 @@ interface ICommonDebugArguments { // Show return values of functions while stepping. showReturnValue?: boolean; subProcess?: boolean; + // An absolute path to local directory with source. + pathMappings?: { localRoot: string; remoteRoot: string }[]; } export interface IKnownAttachDebugArguments extends ICommonDebugArguments { workspaceFolder?: string; - // An absolute path to local directory with source. + customDebugger?: boolean; + // localRoot and remoteRoot are deprecated (replaced by pathMappings). localRoot?: string; remoteRoot?: string; - pathMappings?: { localRoot: string; remoteRoot: string }[]; - customDebugger?: boolean; } export interface IKnownLaunchRequestArguments extends ICommonDebugArguments { From db8da459730370e241325a22db77b8657af7f64e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 14:51:26 -0600 Subject: [PATCH 03/22] Use BaseConfigurationResolver.isLocalHost(). --- .../debugger/extension/configuration/resolvers/attach.ts | 3 +-- .../extension/configuration/resolvers/attach.unit.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/extension/configuration/resolvers/attach.ts b/src/client/debugger/extension/configuration/resolvers/attach.ts index cd60af3168cf..17ce009e3193 100644 --- a/src/client/debugger/extension/configuration/resolvers/attach.ts +++ b/src/client/debugger/extension/configuration/resolvers/attach.ts @@ -94,8 +94,7 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver= 0) { + if (workspaceFolder && this.isLocalHost(debugConfiguration.host)) { let configPathMappings; if (debugConfiguration.pathMappings!.length === 0) { configPathMappings = [{ 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 bcb3294fa306..99e76d172991 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -151,7 +151,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('localRoot', localRoot); }); - ['localhost', '127.0.0.1', '::1'].forEach(host => { + ['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'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); From 8c6f39d0989ca4d536e7eadbb78c9a638bc7e143 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 15:05:28 -0600 Subject: [PATCH 04/22] Use test-specific path separator rather than the native one. --- .../resolvers/attach.unit.test.ts | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) 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 99e76d172991..0aef23b8d4e7 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -6,7 +6,6 @@ // tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this 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'; @@ -23,6 +22,10 @@ getNamesAndValues(OSType).forEach(os => { if (os.value === OSType.Unknown) { return; } + function pathJoin(...parts: string[]): string { + return parts.join(os.name === 'Windows' ? '\\' : '/'); + } + suite(`Debugging - Config Resolver attach, OS = ${os.name}`, () => { let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; @@ -129,7 +132,7 @@ getNamesAndValues(OSType).forEach(os => { 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'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); @@ -143,7 +146,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -156,7 +159,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -174,15 +177,15 @@ getNamesAndValues(OSType).forEach(os => { } const activeFile = 'xyz.py'; - const workspaceFolder = createMoqWorkspaceFolder(path.join('C:', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const lowercasedLocalRoot = path.join('c:', 'Debug', 'Python_Path'); + const lowercasedLocalRoot = pathJoin('c:', 'Debug', 'Python_Path'); expect(pathMappings![0].localRoot).to.be.equal(lowercasedLocalRoot); }); @@ -192,24 +195,24 @@ getNamesAndValues(OSType).forEach(os => { } const activeFile = 'xyz.py'; - const workspaceFolder = createMoqWorkspaceFolder(path.join('C:', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugPathMappings = [ { localRoot: path.join('${workspaceFolder}', localRoot), remoteRoot: '/app/' }]; + const debugPathMappings = [ { localRoot: pathJoin('${workspaceFolder}', localRoot), remoteRoot: '/app/' }]; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, pathMappings: debugPathMappings, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const lowercasedLocalRoot = path.join('c:', 'Debug', 'Python_Path', localRoot); + const lowercasedLocalRoot = pathJoin('c:', 'Debug', 'Python_Path', localRoot); expect(pathMappings![0].localRoot).to.be.equal(lowercasedLocalRoot); }); 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')); + const workspaceFolder = createMoqWorkspaceFolder(pathJoin('Server', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -224,7 +227,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -239,7 +242,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; @@ -253,7 +256,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; @@ -267,7 +270,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -279,7 +282,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const port = 12341234; @@ -291,7 +294,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); @@ -353,7 +356,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = path.join('usr', 'desktop'); + const defaultWorkspace = pathJoin('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); From c1a7cc4726a429079f62c4f91c615a049b1e6de3 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 15:17:07 -0600 Subject: [PATCH 05/22] Do not skip tests for non-windows. --- .../resolvers/attach.unit.test.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) 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 0aef23b8d4e7..bf8ed39fc781 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -171,11 +171,7 @@ getNamesAndValues(OSType).forEach(os => { 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 (os.name !== 'Windows') { - return this.skip(); - } - + test(`Ensure drive letter is lower cased for local path mappings on Windows when host is '${host}'`, async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); @@ -185,15 +181,13 @@ getNamesAndValues(OSType).forEach(os => { const localRoot = `Debug_PythonPath_${new Date().toString()}`; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const lowercasedLocalRoot = pathJoin('c:', 'Debug', 'Python_Path'); - expect(pathMappings![0].localRoot).to.be.equal(lowercasedLocalRoot); + const expected = os.name === 'Windows' + ? pathJoin('c:', 'Debug', 'Python_Path') + : pathJoin('C:', 'Debug', 'Python_Path'); + expect(pathMappings![0].localRoot).to.be.equal(expected); }); - 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 (os.name !== 'Windows') { - return this.skip(); - } - + test(`Ensure drive letter is lower cased for local path mappings on Windows when host is '${host}' and with existing path mappings`, async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); @@ -204,9 +198,11 @@ getNamesAndValues(OSType).forEach(os => { const debugPathMappings = [ { localRoot: pathJoin('${workspaceFolder}', localRoot), remoteRoot: '/app/' }]; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, pathMappings: debugPathMappings, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const lowercasedLocalRoot = pathJoin('c:', 'Debug', 'Python_Path', localRoot); - expect(pathMappings![0].localRoot).to.be.equal(lowercasedLocalRoot); + const expected = os.name === 'Windows' + ? pathJoin('c:', 'Debug', 'Python_Path', localRoot) + : pathJoin('C:', 'Debug', 'Python_Path', localRoot); + expect(pathMappings![0].localRoot).to.be.equal(expected); }); test(`Ensure local path mappings are not modified when not pointing to a local drive when host is '${host}'`, async () => { const activeFile = 'xyz.py'; From 2f106283f2b960e22132d433297dc982340349bb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 14 Aug 2019 16:18:49 -0600 Subject: [PATCH 06/22] Factor out BaseConfigurationResolver.fixUpPathMappings(). --- .../configuration/resolvers/attach.ts | 47 +++++---------- .../extension/configuration/resolvers/base.ts | 59 +++++++++++++++++-- .../configuration/resolvers/launch.ts | 4 +- src/client/debugger/types.ts | 6 +- .../configuration/resolvers/base.unit.test.ts | 11 +++- 5 files changed, 87 insertions(+), 40 deletions(-) diff --git a/src/client/debugger/extension/configuration/resolvers/attach.ts b/src/client/debugger/extension/configuration/resolvers/attach.ts index 17ce009e3193..9b8f20b41fe6 100644 --- a/src/client/debugger/extension/configuration/resolvers/attach.ts +++ b/src/client/debugger/extension/configuration/resolvers/attach.ts @@ -8,17 +8,18 @@ import { CancellationToken, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService } from '../../../../common/types'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; import { AttachRequestArguments, DebugOptions } from '../../../types'; import { BaseConfigurationResolver } from './base'; @injectable() export class AttachConfigurationResolver extends BaseConfigurationResolver { - constructor(@inject(IWorkspaceService) workspaceService: IWorkspaceService, + constructor( + @inject(IWorkspaceService) workspaceService: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, - @inject(IPlatformService) private readonly platformService: IPlatformService, - @inject(IConfigurationService) configurationService: IConfigurationService) { - super(workspaceService, documentManager, configurationService); + @inject(IPlatformService) platformService: IPlatformService, + @inject(IConfigurationService) configurationService: IConfigurationService + ) { + super(workspaceService, documentManager, platformService, configurationService); } public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: AttachRequestArguments, _token?: CancellationToken): Promise { const workspaceFolder = this.getWorkspaceFolder(folder); @@ -94,33 +95,15 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver ({ - localRoot: systemVariables.resolveAny(mappedLocalRoot), - remoteRoot - })); - } - // If on Windows, lowercase the drive letter for path mappings. - let pathMappings = configPathMappings; - if (this.platformService.isWindows) { - pathMappings = configPathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => { - let localRoot = windowsLocalRoot; - if (windowsLocalRoot.match(/^[A-Z]:/)) { - localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`; - } - return { localRoot, remoteRoot }; - }); - } - debugConfiguration.pathMappings = pathMappings; + if (this.isLocalHost(debugConfiguration.host)) { + const pathMappings = this.fixUpPathMappings( + debugConfiguration.pathMappings || [], + workspaceFolder ? workspaceFolder.fsPath : '', + workspaceFolder ? workspaceFolder.fsPath : '' + ); + debugConfiguration.pathMappings = pathMappings.length > 0 + ? pathMappings + : undefined; } this.sendTelemetry('attach', debugConfiguration); } diff --git a/src/client/debugger/extension/configuration/resolvers/base.ts b/src/client/debugger/extension/configuration/resolvers/base.ts index 64c9015f88ba..515e6d5ca510 100644 --- a/src/client/debugger/extension/configuration/resolvers/base.ts +++ b/src/client/debugger/extension/configuration/resolvers/base.ts @@ -3,27 +3,34 @@ 'use strict'; -// tslint:disable:no-invalid-template-strings +// tslint:disable:no-invalid-template-strings no-suspicious-comment import { injectable } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; import { PYTHON_LANGUAGE } from '../../../../common/constants'; +import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService } from '../../../../common/types'; +import { SystemVariables } from '../../../../common/variables/systemVariables'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; import { DebuggerTelemetry } from '../../../../telemetry/types'; -import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../../../types'; +import { + AttachRequestArguments, DebugOptions, LaunchRequestArguments, PathMapping +} from '../../../types'; import { PythonPathSource } from '../../types'; import { IDebugConfigurationResolver } from '../types'; @injectable() export abstract class BaseConfigurationResolver implements IDebugConfigurationResolver { protected pythonPathSource: PythonPathSource = PythonPathSource.launchJson; - constructor(protected readonly workspaceService: IWorkspaceService, + constructor( + protected readonly workspaceService: IWorkspaceService, protected readonly documentManager: IDocumentManager, - protected readonly configurationService: IConfigurationService) { } + protected readonly platformService: IPlatformService, + protected readonly configurationService: IConfigurationService + ) { } public abstract resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): Promise; protected getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { if (folder) { @@ -71,6 +78,50 @@ export abstract class BaseConfigurationResolver im const LocalHosts = ['localhost', '127.0.0.1', '::1']; return (hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0) ? true : false; } + protected fixUpPathMappings( + pathMappings: PathMapping[], + defaultLocalRoot?: string, + defaultRemoteRoot?: string + ): PathMapping[] { + if (!defaultLocalRoot) { + return []; + } + if (!defaultRemoteRoot) { + defaultRemoteRoot = defaultLocalRoot; + } + + if (pathMappings.length === 0) { + pathMappings = [ + { + localRoot: defaultLocalRoot, + remoteRoot: defaultRemoteRoot + } + ]; + } else { + // Expand ${workspaceFolder} variable first if necessary. + const systemVariables = new SystemVariables(defaultLocalRoot); + pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({ + localRoot: systemVariables.resolveAny(mappedLocalRoot), + // TODO: Apply to remoteRoot too? + remoteRoot + })); + } + + // If on Windows, lowercase the drive letter for path mappings. + // TODO: Apply even if no localRoot? + if (this.platformService.isWindows) { + // TODO: Apply to remoteRoot too? + pathMappings = pathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => { + let localRoot = windowsLocalRoot; + if (windowsLocalRoot.match(/^[A-Z]:/)) { + localRoot = `${windowsLocalRoot[0].toLowerCase()}${windowsLocalRoot.substr(1)}`; + } + return { localRoot, remoteRoot }; + }); + } + + return pathMappings; + } protected isDebuggingFlask(debugConfiguration: Partial) { return (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK') ? true : false; } diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 80d2872d50e3..13421bb0663b 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -21,11 +21,11 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { const workspaceFolder = this.getWorkspaceFolder(folder); diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 3792bc6e6db1..1241988c1f61 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -22,6 +22,10 @@ export enum DebugOptions { SubProcess = 'Multiprocess' } +export type PathMapping = { + localRoot: string; + remoteRoot: string; +}; interface ICommonDebugArguments { redirectOutput?: boolean; django?: boolean; @@ -37,7 +41,7 @@ interface ICommonDebugArguments { showReturnValue?: boolean; subProcess?: boolean; // An absolute path to local directory with source. - pathMappings?: { localRoot: string; remoteRoot: string }[]; + pathMappings?: PathMapping[]; } export interface IKnownAttachDebugArguments extends ICommonDebugArguments { workspaceFolder?: string; 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 8e2a4e70bdf8..f4306938f8b7 100644 --- a/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/base.unit.test.ts @@ -16,6 +16,8 @@ import { IDocumentManager, IWorkspaceService } from '../../../../../client/commo import { WorkspaceService } from '../../../../../client/common/application/workspace'; import { ConfigurationService } from '../../../../../client/common/configuration/service'; import { PYTHON_LANGUAGE } from '../../../../../client/common/constants'; +import { PlatformService } from '../../../../../client/common/platform/platformService'; +import { IPlatformService } from '../../../../../client/common/platform/types'; import { IConfigurationService } from '../../../../../client/common/types'; import { BaseConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/base'; import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; @@ -46,13 +48,20 @@ suite('Debugging - Config Resolver', () => { } let resolver: BaseResolver; let workspaceService: IWorkspaceService; + let platformService: IPlatformService; let documentManager: IDocumentManager; let configurationService: IConfigurationService; setup(() => { workspaceService = mock(WorkspaceService); documentManager = mock(DocumentManager); + platformService = mock(PlatformService); configurationService = mock(ConfigurationService); - resolver = new BaseResolver(instance(workspaceService), instance(documentManager), instance(configurationService)); + resolver = new BaseResolver( + instance(workspaceService), + instance(documentManager), + instance(platformService), + instance(configurationService) + ); }); test('Program should return filepath of active editor if file is python', () => { From d1a1055d5c456dfa17e8c68169d8b322fb4268dd Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 19 Aug 2019 10:52:08 -0600 Subject: [PATCH 07/22] Factor out AttachConfigurationResolver.resolvePathMappings(). --- .../configuration/resolvers/attach.ts | 42 ++++++++++++------- .../resolvers/attach.unit.test.ts | 2 +- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/client/debugger/extension/configuration/resolvers/attach.ts b/src/client/debugger/extension/configuration/resolvers/attach.ts index 9b8f20b41fe6..32fe7a73b4bf 100644 --- a/src/client/debugger/extension/configuration/resolvers/attach.ts +++ b/src/client/debugger/extension/configuration/resolvers/attach.ts @@ -8,7 +8,7 @@ import { CancellationToken, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService } from '../../../../common/types'; -import { AttachRequestArguments, DebugOptions } from '../../../types'; +import { AttachRequestArguments, DebugOptions, PathMapping } from '../../../types'; import { BaseConfigurationResolver } from './base'; @injectable() @@ -84,27 +84,39 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver 0 + } + return pathMappings.length > 0 ? pathMappings : undefined; - } - this.sendTelemetry('attach', debugConfiguration); } } 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 bf8ed39fc781..8880e680eec8 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -231,7 +231,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('localRoot', localRoot); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - expect(pathMappings).to.be.lengthOf(0); + expect(pathMappings || []).to.be.lengthOf(0); }); }); test('Ensure \'localRoot\' and \'remoteRoot\' is used', async () => { From 8b645f4158c95b871ccf5c5f25b1952a78aaa8ca Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 19 Aug 2019 13:22:21 -0600 Subject: [PATCH 08/22] Fix up pathMappings for launch config. --- .../configuration/resolvers/launch.ts | 12 + .../resolvers/attach.unit.test.ts | 3 + .../resolvers/launch.unit.test.ts | 1097 ++++++++++------- 3 files changed, 678 insertions(+), 434 deletions(-) diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 13421bb0663b..8df8f81cd236 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -123,6 +123,18 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver 0) { + pathMappings = this.fixUpPathMappings( + pathMappings || [], + workspaceFolder ? workspaceFolder.fsPath : '' + ); + } + debugConfiguration.pathMappings = pathMappings.length > 0 + ? pathMappings + : undefined; + } this.sendTelemetry( debugConfiguration.request as 'launch' | 'test', debugConfiguration 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 8880e680eec8..9567bff0e682 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -186,6 +186,7 @@ getNamesAndValues(OSType).forEach(os => { ? pathJoin('c:', 'Debug', 'Python_Path') : pathJoin('C:', 'Debug', 'Python_Path'); 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 () => { const activeFile = 'xyz.py'; @@ -203,6 +204,7 @@ getNamesAndValues(OSType).forEach(os => { ? pathJoin('c:', 'Debug', 'Python_Path', localRoot) : pathJoin('C:', 'Debug', 'Python_Path', localRoot); 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'; @@ -216,6 +218,7 @@ getNamesAndValues(OSType).forEach(os => { 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 => { 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 8853a291d988..4afe46061c91 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -15,458 +15,687 @@ import { PYTHON_LANGUAGE } from '../../../../../client/common/constants'; import { IPlatformService } from '../../../../../client/common/platform/types'; import { IPythonExecutionFactory, IPythonExecutionService } from '../../../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../../../client/common/types'; +import { getNamesAndValues } from '../../../../../client/common/utils/enum'; +import { OSType } from '../../../../../client/common/utils/platform'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; import { IDebugEnvironmentVariablesService } from '../../../../../client/debugger/extension/configuration/resolvers/helper'; import { LaunchConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/launch'; import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; -suite('Debugging - Config Resolver Launch', () => { - let debugProvider: DebugConfigurationProvider; - let platformService: TypeMoq.IMock; - let pythonExecutionService: TypeMoq.IMock; - let helper: TypeMoq.IMock; - let workspaceService: TypeMoq.IMock; - 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; +getNamesAndValues(OSType).forEach(os => { + if (os.value === OSType.Unknown) { + return; } - function setupIoc(pythonPath: string, workspaceFolder?: WorkspaceFolder, isWindows: boolean = false, isMac: boolean = false, isLinux: boolean = false) { - const confgService = TypeMoq.Mock.ofType(); - workspaceService = TypeMoq.Mock.ofType(); - documentManager = TypeMoq.Mock.ofType(); - - platformService = TypeMoq.Mock.ofType(); - diagnosticsService = TypeMoq.Mock.ofType(); - debugEnvHelper = TypeMoq.Mock.ofType(); - - pythonExecutionService = TypeMoq.Mock.ofType(); - helper = TypeMoq.Mock.ofType(); - pythonExecutionService.setup((x: any) => x.then).returns(() => undefined); - const factory = TypeMoq.Mock.ofType(); - factory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(pythonExecutionService.object)); - helper.setup(h => h.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); - diagnosticsService - .setup(h => h.validatePythonPath(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)); - - const settings = TypeMoq.Mock.ofType(); - settings.setup(s => s.pythonPath).returns(() => pythonPath); - if (workspaceFolder) { - settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); - } - confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - setupOs(isWindows, isMac, isLinux); - debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); - - debugProvider = new LaunchConfigurationResolver( - workspaceService.object, - documentManager.object, - diagnosticsService.object, - platformService.object, - confgService.object, - debugEnvHelper.object); + function pathJoin(...parts: string[]): string { + return parts.join(os.name === 'Windows' ? '\\' : '/'); } - function setupActiveEditor(fileName: string | undefined, languageId: string) { - 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); + + suite(`Debugging - Config Resolver Launch, OS = ${os.name}`, () => { + let debugProvider: DebugConfigurationProvider; + let platformService: TypeMoq.IMock; + let pythonExecutionService: TypeMoq.IMock; + let helper: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + let documentManager: TypeMoq.IMock; + let diagnosticsService: TypeMoq.IMock; + let debugEnvHelper: TypeMoq.IMock; + const debugOptionsAvailable = [DebugOptions.RedirectOutput]; + if (os.value === OSType.Windows) { + debugOptionsAvailable.push(DebugOptions.FixFilePathCase); + debugOptionsAvailable.push(DebugOptions.WindowsClient); } else { - documentManager.setup(d => d.activeTextEditor).returns(() => undefined); + debugOptionsAvailable.push(DebugOptions.UnixClient); } - } - function setupWorkspaces(folders: string[]) { - const workspaceFolders = folders.map(createMoqWorkspaceFolder); - workspaceService.setup(w => w.workspaceFolders).returns(() => workspaceFolders); - } - function setupOs(isWindows: boolean, isMac: boolean, isLinux: boolean) { - platformService.setup(p => p.isWindows).returns(() => isWindows); - platformService.setup(p => p.isMac).returns(() => isMac); - platformService.setup(p => p.isLinux).returns(() => isLinux); - } - 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); - - 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.have.property('program', pythonFile); - expect(debugConfig).to.have.property('cwd'); - expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); - expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(__dirname, '.env2').toLowerCase()); - 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'; - setupIoc(pythonPath, workspaceFolder); - setupActiveEditor(pythonFile, PYTHON_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', 'python'); - expect(debugConfig).to.have.property('request', 'launch'); - expect(debugConfig).to.have.property('program', pythonFile); - expect(debugConfig).to.have.property('cwd'); - expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); - expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(__dirname, '.env2').toLowerCase()); - 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'; - setupIoc(pythonPath, createMoqWorkspaceFolder(path.dirname(pythonFile))); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - setupWorkspaces([]); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); - 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.have.property('program', pythonFile); - expect(debugConfig).to.have.property('cwd'); - expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); - expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(filePath, '.env2').toLowerCase()); - 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()}`; - setupIoc(pythonPath); - setupActiveEditor(undefined, PYTHON_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', 'python'); - expect(debugConfig).to.have.property('request', 'launch'); - expect(debugConfig).to.have.property('program', ''); - expect(debugConfig).not.to.have.property('cwd'); - expect(debugConfig).not.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'; - setupIoc(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', 'python'); - expect(debugConfig).to.have.property('request', 'launch'); - expect(debugConfig).to.have.property('program', ''); - expect(debugConfig).not.to.have.property('cwd'); - expect(debugConfig).not.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'; - const defaultWorkspace = path.join('usr', 'desktop'); - setupIoc(pythonPath, createMoqWorkspaceFolder(defaultWorkspace)); - setupActiveEditor(activeFile, PYTHON_LANGUAGE); - setupWorkspaces([defaultWorkspace]); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); - 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.have.property('program', activeFile); - expect(debugConfig).to.have.property('cwd'); - expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); - expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(filePath, '.env2').toLowerCase()); - 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); - setupIoc(pythonPath); - setupActiveEditor(activeFile, PYTHON_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); - setupIoc(pythonPath); - setupActiveEditor(activeFile, PYTHON_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 debugger', async () => { - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_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('showReturnValue', true); - expect(debugConfig).to.have.property('debugOptions'); - expect((debugConfig as any).debugOptions).to.be.deep.equal([DebugOptions.ShowReturnValue, DebugOptions.RedirectOutput]); - }); - test('Test defaults of python debugger', async () => { - if ('python' === DebuggerTypeName) { - return; + debugOptionsAvailable.push(DebugOptions.ShowReturnValue); + function createMoqWorkspaceFolder(folderPath: string) { + const folder = TypeMoq.Mock.ofType(); + folder.setup(f => f.uri).returns(() => Uri.file(folderPath)); + return folder.object; } - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); - - 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([DebugOptions.RedirectOutput]); - }); - test('Test overriding defaults of debugger', async () => { - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { redirectOutput: false, justMyCode: false } as LaunchRequestArguments); - - expect(debugConfig).to.have.property('console', 'integratedTerminal'); - expect(debugConfig).to.have.property('stopOnEntry', false); - expect(debugConfig).to.have.property('showReturnValue', true); - expect(debugConfig).to.have.property('justMyCode', false); - expect(debugConfig).to.have.property('debugOptions'); - expect((debugConfig as any).debugOptions).to.be.deep.equal([DebugOptions.DebugStdLib, DebugOptions.ShowReturnValue]); - }); - const testsForJustMyCode = - [ - { - justMyCode: false, - debugStdLib: true, - expectedResult: false - }, - { - justMyCode: false, - debugStdLib: false, - expectedResult: false - }, - { - justMyCode: false, - debugStdLib: undefined, - expectedResult: false - }, - { - justMyCode: true, - debugStdLib: false, - expectedResult: true - }, - { - justMyCode: true, - debugStdLib: true, - expectedResult: true - }, - { - justMyCode: true, - debugStdLib: undefined, - expectedResult: true - }, - { - justMyCode: undefined, - debugStdLib: false, - expectedResult: true - }, - { - justMyCode: undefined, - debugStdLib: true, - expectedResult: false - }, - { - justMyCode: undefined, - debugStdLib: undefined, - expectedResult: true + function setupIoc(pythonPath: string, workspaceFolder?: WorkspaceFolder) { + const confgService = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); + documentManager = TypeMoq.Mock.ofType(); + + platformService = TypeMoq.Mock.ofType(); + diagnosticsService = TypeMoq.Mock.ofType(); + debugEnvHelper = TypeMoq.Mock.ofType(); + + pythonExecutionService = TypeMoq.Mock.ofType(); + helper = TypeMoq.Mock.ofType(); + pythonExecutionService.setup((x: any) => x.then).returns(() => undefined); + const factory = TypeMoq.Mock.ofType(); + factory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(pythonExecutionService.object)); + helper.setup(h => h.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); + diagnosticsService + .setup(h => h.validatePythonPath(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(true)); + + const settings = TypeMoq.Mock.ofType(); + settings.setup(s => s.pythonPath).returns(() => pythonPath); + if (workspaceFolder) { + settings.setup(s => s.envFile).returns(() => pathJoin(workspaceFolder!.uri.fsPath, '.env2')); } - ]; - test('Ensure justMyCode property is correctly derived from debugStdLib', async () => { - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - testsForJustMyCode.forEach(async testParams => { - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugStdLib: testParams.debugStdLib, justMyCode: testParams.justMyCode } as LaunchRequestArguments); - expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); - }); - }); - async function testFixFilePathCase(isWindows: boolean, isMac: boolean, isLinux: boolean) { - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath, undefined, isWindows, isMac, isLinux); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); - if (isWindows) { - expect(debugConfig).to.have.property('debugOptions').contains(DebugOptions.FixFilePathCase); - } else { - expect(debugConfig).to.have.property('debugOptions').not.contains(DebugOptions.FixFilePathCase); + confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + platformService.setup(p => p.isWindows).returns(() => os.value === OSType.Windows); + platformService.setup(p => p.isMac).returns(() => os.value === OSType.OSX); + platformService.setup(p => p.isLinux).returns(() => os.value === OSType.Linux); + debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); + + debugProvider = new LaunchConfigurationResolver( + workspaceService.object, + documentManager.object, + diagnosticsService.object, + platformService.object, + confgService.object, + debugEnvHelper.object); } - } - test('Test fixFilePathCase for Windows', async () => { - await testFixFilePathCase(true, false, false); - }); - test('Test fixFilePathCase for Linux', async () => { - await testFixFilePathCase(false, false, true); - }); - test('Test fixFilePathCase for Mac', async () => { - await testFixFilePathCase(false, true, false); - }); - test('Jinja added for Pyramid', async () => { - const workspacePath = path.join('usr', 'development', 'wksp1'); - const pythonPath = path.join(workspacePath, 'env', 'bin', 'python'); - const workspaceFolder = createMoqWorkspaceFolder(workspacePath); - const pythonFile = 'xyz.py'; + function setupActiveEditor(fileName: string | undefined, languageId: string) { + 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); + } + } + function setupWorkspaces(folders: string[]) { + const workspaceFolders = folders.map(createMoqWorkspaceFolder); + workspaceService.setup(w => w.workspaceFolders).returns(() => workspaceFolders); + } + 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); + + 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.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd'); + expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + expect(debugConfig).to.have.property('envFile'); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(__dirname, '.env2').toLowerCase()); + 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'; + setupIoc(pythonPath, workspaceFolder); + setupActiveEditor(pythonFile, PYTHON_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', 'python'); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd'); + expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + expect(debugConfig).to.have.property('envFile'); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(__dirname, '.env2').toLowerCase()); + 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'; + setupIoc(pythonPath, createMoqWorkspaceFolder(path.dirname(pythonFile))); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + 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.have.property('program', pythonFile); + expect(debugConfig).to.have.property('cwd'); + expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + expect(debugConfig).to.have.property('envFile'); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(filePath, '.env2').toLowerCase()); + 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()}`; + setupIoc(pythonPath); + setupActiveEditor(undefined, PYTHON_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', 'python'); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', ''); + expect(debugConfig).not.to.have.property('cwd'); + expect(debugConfig).not.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'; + setupIoc(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', 'python'); + expect(debugConfig).to.have.property('request', 'launch'); + expect(debugConfig).to.have.property('program', ''); + expect(debugConfig).not.to.have.property('cwd'); + expect(debugConfig).not.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'; + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupIoc(pythonPath, createMoqWorkspaceFolder(defaultWorkspace)); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); + 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.have.property('program', activeFile); + expect(debugConfig).to.have.property('cwd'); + expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + expect(debugConfig).to.have.property('envFile'); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(filePath, '.env2').toLowerCase()); + 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 \'port\' is left unaltered', async () => { + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); - setupIoc(pythonPath, undefined, false, false, true); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + const port = 12341234; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { port, request: 'launch' } as any as DebugConfiguration); - const options = { debugOptions: [DebugOptions.Pyramid], pyramid: true }; + expect(debugConfig).to.have.property('port', port); + }); + test('Ensure \'localRoot\' is left unaltered', async () => { + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); - 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); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { module: 'flask' } as any as DebugConfiguration); - - expect(debugConfig).to.have.property('debugOptions'); - expect((debugConfig as any).debugOptions).contains(DebugOptions.RedirectOutput); - 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); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - diagnosticsService.reset(); - diagnosticsService - .setup(h => h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(false)) - .verifiable(TypeMoq.Times.once()); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { 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); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - - diagnosticsService.reset(); - diagnosticsService - .setup(h => h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(true)) - .verifiable(TypeMoq.Times.once()); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { redirectOutput: false, pythonPath } as LaunchRequestArguments); - - diagnosticsService.verifyAll(); - expect(debugConfig).to.not.be.equal(undefined, 'is undefined'); - }); - async function testSetting(requestType: 'launch' | 'attach', settings: Record, debugOptionName: DebugOptions, mustHaveDebugOption: boolean) { - setupIoc('pythonPath'); - const debugConfiguration: DebugConfiguration = { request: requestType, type: 'python', name: '', ...settings }; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, debugConfiguration); - if (mustHaveDebugOption) { - expect((debugConfig as any).debugOptions).contains(debugOptionName); - } else { - expect((debugConfig as any).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 () => { - await testSetting(requestType, {}, DebugOptions.SubProcess, false); + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'launch' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('localRoot', localRoot); + }); + test('Ensure \'remoteRoot\' is left unaltered', async () => { + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { remoteRoot, request: 'launch' } as any as DebugConfiguration); + + 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); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'launch' } as any as DebugConfiguration); + + 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); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const expected = { + localRoot: `Debug_PythonPath_Local_Root_${new Date().toString()}`, + remoteRoot: `Debug_PythonPath_Remote_Root_${new Date().toString()}` + }; + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: '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 = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: 'launch', + pathMappings: [{ + localRoot: '${workspaceFolder}/spam', + remoteRoot: '${workspaceFolder}/spam' + }] + } as any as DebugConfiguration + ); + + const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; + expect(pathMappings).to.be.deep.equal([{ + localRoot: `${workspaceFolder.uri.fsPath}/spam`, + remoteRoot: '${workspaceFolder}/spam' + }]); + }); + test('Ensure path mappings are not automatically added if missing', async () => { + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: '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); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: '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); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: 'launch', + localRoot: localRoot, + pathMappings: [{ + localRoot: '/spam', + remoteRoot: '.' + }] + } as any as DebugConfiguration + ); + + expect(debugConfig).to.have.property('localRoot', localRoot); + const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; + expect(pathMappings).to.be.deep.equal([{ + localRoot: '/spam', + remoteRoot: '.' + }]); + }); + test('Ensure drive letter is lower cased for local path mappings on Windows when with existing path mappings', async () => { + const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + const localRoot = pathJoin(workspaceFolder.uri.fsPath, 'app'); + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: 'launch', + pathMappings: [{ + localRoot: localRoot, + remoteRoot: '/app/' + }] + } as any as DebugConfiguration + ); + + const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; + const expected = os.name === 'Windows' + ? `c${localRoot.substring(1)}` + : localRoot; + expect(pathMappings).to.deep.equal([{ + localRoot: expected, + remoteRoot: '/app/' + }]); + }); + test('Ensure local path mappings are not modified when not pointing to a local drive', async () => { + const workspaceFolder = createMoqWorkspaceFolder(pathJoin('Server', 'Debug', 'Python_Path')); + setupActiveEditor('spam.py', PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!( + workspaceFolder, + { + request: 'launch', + pathMappings: [{ + localRoot: '/spam', + remoteRoot: '.' + }] + } as any as DebugConfiguration + ); + + const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; + expect(pathMappings).to.deep.equal([{ + localRoot: '/spam', + remoteRoot: '.' + }]); + }); + 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); + setupIoc(pythonPath); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('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(`Must not contain Sub Process setting=false (${requestType})`, async () => { - await testSetting(requestType, { subProcess: false }, DebugOptions.SubProcess, false); + test('Ensure hardcoded pythonPath is left unaltered', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupIoc(pythonPath); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + const defaultWorkspace = pathJoin('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(`Must not contain Sub Process setting=true (${requestType})`, async () => { - await testSetting(requestType, { subProcess: true }, DebugOptions.SubProcess, true); + test('Test defaults of debugger', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_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('showReturnValue', true); + expect(debugConfig).to.have.property('debugOptions'); + const expectedOptions = [ + DebugOptions.ShowReturnValue, + DebugOptions.RedirectOutput + ]; + if (os.name === 'Windows') { + expectedOptions.push( + DebugOptions.FixFilePathCase + ); + } + expect((debugConfig as any).debugOptions).to.be.deep.equal(expectedOptions); + }); + test('Test defaults of python debugger', async () => { + if ('python' === DebuggerTypeName) { + return; + } + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); + + 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([ + DebugOptions.RedirectOutput + ]); + }); + test('Test overriding defaults of debugger', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { redirectOutput: false, justMyCode: false } as LaunchRequestArguments); + + expect(debugConfig).to.have.property('console', 'integratedTerminal'); + expect(debugConfig).to.have.property('stopOnEntry', false); + expect(debugConfig).to.have.property('showReturnValue', true); + expect(debugConfig).to.have.property('justMyCode', false); + expect(debugConfig).to.have.property('debugOptions'); + const expectedOptions = [ + DebugOptions.DebugStdLib, + DebugOptions.ShowReturnValue + ]; + if (os.name === 'Windows') { + expectedOptions.push( + DebugOptions.FixFilePathCase + ); + } + expect((debugConfig as any).debugOptions).to.be.deep.equal(expectedOptions); + }); + const testsForJustMyCode = + [ + { + justMyCode: false, + debugStdLib: true, + expectedResult: false + }, + { + justMyCode: false, + debugStdLib: false, + expectedResult: false + }, + { + justMyCode: false, + debugStdLib: undefined, + expectedResult: false + }, + { + justMyCode: true, + debugStdLib: false, + expectedResult: true + }, + { + justMyCode: true, + debugStdLib: true, + expectedResult: true + }, + { + justMyCode: true, + debugStdLib: undefined, + expectedResult: true + }, + { + justMyCode: undefined, + debugStdLib: false, + expectedResult: true + }, + { + justMyCode: undefined, + debugStdLib: true, + expectedResult: false + }, + { + justMyCode: undefined, + debugStdLib: undefined, + expectedResult: true + } + ]; + test('Ensure justMyCode property is correctly derived from debugStdLib', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + testsForJustMyCode.forEach(async testParams => { + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugStdLib: testParams.debugStdLib, justMyCode: testParams.justMyCode } as LaunchRequestArguments); + expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); + }); + }); + test('Test fixFilePathCase', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); + if (os.name === '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 = pathJoin('usr', 'development', 'wksp1'); + const pythonPath = pathJoin(workspacePath, 'env', 'bin', 'python'); + const workspaceFolder = createMoqWorkspaceFolder(workspacePath); + const pythonFile = 'xyz.py'; + + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const options = { 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); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { module: 'flask' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).contains(DebugOptions.RedirectOutput); + 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); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + diagnosticsService.reset(); + diagnosticsService + .setup(h => h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(false)) + .verifiable(TypeMoq.Times.once()); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { 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); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + diagnosticsService.reset(); + diagnosticsService + .setup(h => h.validatePythonPath(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(true)) + .verifiable(TypeMoq.Times.once()); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { redirectOutput: false, pythonPath } as LaunchRequestArguments); + + diagnosticsService.verifyAll(); + expect(debugConfig).to.not.be.equal(undefined, 'is undefined'); + }); + async function testSetting(requestType: 'launch' | 'attach', settings: Record, debugOptionName: DebugOptions, mustHaveDebugOption: boolean) { + setupIoc('pythonPath'); + const debugConfiguration: DebugConfiguration = { request: requestType, type: 'python', name: '', ...settings }; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, debugConfiguration); + if (mustHaveDebugOption) { + expect((debugConfig as any).debugOptions).contains(debugOptionName); + } else { + expect((debugConfig as any).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 () => { + await testSetting(requestType, {}, DebugOptions.SubProcess, false); + }); + 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 () => { + await testSetting(requestType, { subProcess: true }, DebugOptions.SubProcess, true); + }); }); }); }); From 832d07242e45d695329001956a36d1b50ac2e250 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 19 Aug 2019 15:29:38 -0600 Subject: [PATCH 09/22] Add a NEWS entry. --- news/2 Fixes/3568.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/3568.md diff --git a/news/2 Fixes/3568.md b/news/2 Fixes/3568.md new file mode 100644 index 000000000000..619227a39bbe --- /dev/null +++ b/news/2 Fixes/3568.md @@ -0,0 +1 @@ +Add support for the "pathMappings" setting in "launch" debug configs. From 2d93fc7a7101d63de34157ffd5c8f6ec1a0a733b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 19 Aug 2019 15:32:54 -0600 Subject: [PATCH 10/22] Add a comment about not having default path mappings for launch. --- src/client/debugger/extension/configuration/resolvers/launch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 8df8f81cd236..b6c47ba20d73 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -123,6 +123,8 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver 0) { From 234bc5b0571b3ca2d687fcdfea1f58cf9fe6a836 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 14:07:44 -0600 Subject: [PATCH 11/22] Use path.join() in tests. --- .../resolvers/attach.unit.test.ts | 49 ++++++++--------- .../resolvers/launch.unit.test.ts | 54 +++++++++---------- 2 files changed, 52 insertions(+), 51 deletions(-) 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 9567bff0e682..be368c333f30 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -6,6 +6,7 @@ // tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this 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'; @@ -22,9 +23,9 @@ getNamesAndValues(OSType).forEach(os => { if (os.value === OSType.Unknown) { return; } - function pathJoin(...parts: string[]): string { - return parts.join(os.name === 'Windows' ? '\\' : '/'); - } + // None of the behavior tested here relies on the path separator. + // So we can use path.join() even though the tests are OS-specific. + // We could use hard-coded paths instead if we wanted to. suite(`Debugging - Config Resolver attach, OS = ${os.name}`, () => { let serviceContainer: TypeMoq.IMock; @@ -132,7 +133,7 @@ getNamesAndValues(OSType).forEach(os => { 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 = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); @@ -146,7 +147,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -159,7 +160,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -173,9 +174,9 @@ getNamesAndValues(OSType).forEach(os => { }); test(`Ensure drive letter is lower cased for local path mappings on Windows when host is '${host}'`, async () => { const activeFile = 'xyz.py'; - const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(path.join('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -183,34 +184,34 @@ getNamesAndValues(OSType).forEach(os => { const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = os.name === 'Windows' - ? pathJoin('c:', 'Debug', 'Python_Path') - : pathJoin('C:', 'Debug', 'Python_Path'); + ? path.join('c:', 'Debug', 'Python_Path') + : path.join('C:', 'Debug', 'Python_Path'); 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 () => { const activeFile = 'xyz.py'; - const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(path.join('C:', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; - const debugPathMappings = [ { localRoot: pathJoin('${workspaceFolder}', localRoot), remoteRoot: '/app/' }]; + const debugPathMappings = [ { localRoot: path.join('${workspaceFolder}', localRoot), remoteRoot: '/app/' }]; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, pathMappings: debugPathMappings, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; const expected = os.name === 'Windows' - ? pathJoin('c:', 'Debug', 'Python_Path', localRoot) - : pathJoin('C:', 'Debug', 'Python_Path', localRoot); + ? path.join('c:', 'Debug', 'Python_Path', localRoot) + : path.join('C:', 'Debug', 'Python_Path', localRoot); 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(pathJoin('Server', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(path.join('Server', 'Debug', 'Python_Path')); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -226,7 +227,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -241,7 +242,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; @@ -255,7 +256,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; @@ -269,7 +270,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -281,7 +282,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const port = 12341234; @@ -293,7 +294,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); @@ -355,7 +356,7 @@ getNamesAndValues(OSType).forEach(os => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); 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 4afe46061c91..6dc7af99e16a 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -27,9 +27,9 @@ getNamesAndValues(OSType).forEach(os => { if (os.value === OSType.Unknown) { return; } - function pathJoin(...parts: string[]): string { - return parts.join(os.name === 'Windows' ? '\\' : '/'); - } + // None of the behavior tested here relies on the path separator. + // So we can use path.join() even though the tests are OS-specific. + // We could use hard-coded paths instead if we wanted to. suite(`Debugging - Config Resolver Launch, OS = ${os.name}`, () => { let debugProvider: DebugConfigurationProvider; @@ -75,7 +75,7 @@ getNamesAndValues(OSType).forEach(os => { const settings = TypeMoq.Mock.ofType(); settings.setup(s => s.pythonPath).returns(() => pythonPath); if (workspaceFolder) { - settings.setup(s => s.envFile).returns(() => pathJoin(workspaceFolder!.uri.fsPath, '.env2')); + settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); platformService.setup(p => p.isWindows).returns(() => os.value === OSType.Windows); @@ -125,7 +125,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(__dirname, '.env2').toLowerCase()); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(__dirname, '.env2').toLowerCase()); expect(debugConfig).to.have.property('env'); // tslint:disable-next-line:no-any expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); @@ -147,7 +147,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(__dirname, '.env2').toLowerCase()); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(__dirname, '.env2').toLowerCase()); expect(debugConfig).to.have.property('env'); // tslint:disable-next-line:no-any expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); @@ -170,7 +170,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(filePath, '.env2').toLowerCase()); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(filePath, '.env2').toLowerCase()); expect(debugConfig).to.have.property('env'); // tslint:disable-next-line:no-any expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); @@ -217,7 +217,7 @@ getNamesAndValues(OSType).forEach(os => { 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'; - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupIoc(pythonPath, createMoqWorkspaceFolder(defaultWorkspace)); setupActiveEditor(activeFile, PYTHON_LANGUAGE); setupWorkspaces([defaultWorkspace]); @@ -233,7 +233,7 @@ getNamesAndValues(OSType).forEach(os => { expect(debugConfig).to.have.property('cwd'); expect(debugConfig!.cwd!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('envFile'); - expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(pathJoin(filePath, '.env2').toLowerCase()); + expect(debugConfig!.envFile!.toLowerCase()).to.be.equal(path.join(filePath, '.env2').toLowerCase()); expect(debugConfig).to.have.property('env'); // tslint:disable-next-line:no-any expect(Object.keys((debugConfig as any).env)).to.have.lengthOf(0); @@ -241,7 +241,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure \'port\' is left unaltered', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const port = 12341234; @@ -252,7 +252,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure \'localRoot\' is left unaltered', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -263,7 +263,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure \'remoteRoot\' is left unaltered', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -274,7 +274,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure \'localRoot\' and \'remoteRoot\' are not used', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; @@ -286,7 +286,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure non-empty path mappings are used', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const expected = { @@ -307,7 +307,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure replacement in path mappings happens', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugConfig = await debugProvider.resolveDebugConfiguration!( @@ -330,7 +330,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure path mappings are not automatically added if missing', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -348,7 +348,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure path mappings are not automatically added if empty', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -367,7 +367,7 @@ getNamesAndValues(OSType).forEach(os => { test('Ensure path mappings are not automatically added to existing', async () => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const localRoot = `Debug_PythonPath_${new Date().toString()}`; @@ -391,11 +391,11 @@ getNamesAndValues(OSType).forEach(os => { }]); }); test('Ensure drive letter is lower cased for local path mappings on Windows when with existing path mappings', async () => { - const workspaceFolder = createMoqWorkspaceFolder(pathJoin('C:', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(path.join('C:', 'Debug', 'Python_Path')); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); - const localRoot = pathJoin(workspaceFolder.uri.fsPath, 'app'); + const localRoot = path.join(workspaceFolder.uri.fsPath, 'app'); const debugConfig = await debugProvider.resolveDebugConfiguration!( workspaceFolder, @@ -418,9 +418,9 @@ getNamesAndValues(OSType).forEach(os => { }]); }); test('Ensure local path mappings are not modified when not pointing to a local drive', async () => { - const workspaceFolder = createMoqWorkspaceFolder(pathJoin('Server', 'Debug', 'Python_Path')); + const workspaceFolder = createMoqWorkspaceFolder(path.join('Server', 'Debug', 'Python_Path')); setupActiveEditor('spam.py', PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugConfig = await debugProvider.resolveDebugConfiguration!( @@ -446,7 +446,7 @@ getNamesAndValues(OSType).forEach(os => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupIoc(pythonPath); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { pythonPath: '${config:python.pythonPath}' } as any as DebugConfiguration); @@ -459,7 +459,7 @@ getNamesAndValues(OSType).forEach(os => { const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupIoc(pythonPath); setupActiveEditor(activeFile, PYTHON_LANGUAGE); - const defaultWorkspace = pathJoin('usr', 'desktop'); + const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); const debugPythonPath = `Debug_PythonPath_${new Date().toString()}`; @@ -609,8 +609,8 @@ getNamesAndValues(OSType).forEach(os => { } }); test('Jinja added for Pyramid', async () => { - const workspacePath = pathJoin('usr', 'development', 'wksp1'); - const pythonPath = pathJoin(workspacePath, 'env', 'bin', 'python'); + const workspacePath = path.join('usr', 'development', 'wksp1'); + const pythonPath = path.join(workspacePath, 'env', 'bin', 'python'); const workspaceFolder = createMoqWorkspaceFolder(workspacePath); const pythonFile = 'xyz.py'; From 18b4f19b2edbce433bae149d001413450632579b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 14:23:06 -0600 Subject: [PATCH 12/22] Use the OSType enum directly in tests. --- .../resolvers/attach.unit.test.ts | 15 ++++++++------- .../resolvers/launch.unit.test.ts | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) 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 be368c333f30..8b3d66c886fd 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -20,7 +20,8 @@ import { AttachRequestArguments, DebugOptions } from '../../../../../client/debu import { IServiceContainer } from '../../../../../client/ioc/types'; getNamesAndValues(OSType).forEach(os => { - if (os.value === OSType.Unknown) { + const osType: OSType = os.value as OSType; + if (osType === OSType.Unknown) { return; } // None of the behavior tested here relies on the path separator. @@ -36,7 +37,7 @@ getNamesAndValues(OSType).forEach(os => { let configurationService: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; const debugOptionsAvailable = [DebugOptions.RedirectOutput]; - if (os.value === OSType.Windows) { + if (osType === OSType.Windows) { debugOptionsAvailable.push(DebugOptions.FixFilePathCase); debugOptionsAvailable.push(DebugOptions.WindowsClient); } else { @@ -51,9 +52,9 @@ getNamesAndValues(OSType).forEach(os => { fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - platformService.setup(p => p.isWindows).returns(() => os.value === OSType.Windows); - platformService.setup(p => p.isMac).returns(() => os.value === OSType.OSX); - platformService.setup(p => p.isLinux).returns(() => os.value === OSType.Linux); + platformService.setup(p => p.isWindows).returns(() => osType === OSType.Windows); + platformService.setup(p => p.isMac).returns(() => osType === OSType.OSX); + platformService.setup(p => p.isLinux).returns(() => osType === OSType.Linux); documentManager = TypeMoq.Mock.ofType(); debugProvider = new AttachConfigurationResolver(workspaceService.object, documentManager.object, platformService.object, configurationService.object); }); @@ -183,7 +184,7 @@ getNamesAndValues(OSType).forEach(os => { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const expected = os.name === 'Windows' + const expected = osType === OSType.Windows ? path.join('c:', 'Debug', 'Python_Path') : path.join('C:', 'Debug', 'Python_Path'); expect(pathMappings![0].localRoot).to.be.equal(expected); @@ -201,7 +202,7 @@ getNamesAndValues(OSType).forEach(os => { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, pathMappings: debugPathMappings, host, request: 'attach' } as any as DebugConfiguration); const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; - const expected = os.name === 'Windows' + const expected = osType === OSType.Windows ? path.join('c:', 'Debug', 'Python_Path', localRoot) : path.join('C:', 'Debug', 'Python_Path', localRoot); expect(pathMappings![0].localRoot).to.be.equal(expected); 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 6dc7af99e16a..025c4abfed44 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -24,7 +24,8 @@ import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debu import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; getNamesAndValues(OSType).forEach(os => { - if (os.value === OSType.Unknown) { + const osType: OSType = os.value as OSType; + if (osType === OSType.Unknown) { return; } // None of the behavior tested here relies on the path separator. @@ -41,7 +42,7 @@ getNamesAndValues(OSType).forEach(os => { let diagnosticsService: TypeMoq.IMock; let debugEnvHelper: TypeMoq.IMock; const debugOptionsAvailable = [DebugOptions.RedirectOutput]; - if (os.value === OSType.Windows) { + if (osType === OSType.Windows) { debugOptionsAvailable.push(DebugOptions.FixFilePathCase); debugOptionsAvailable.push(DebugOptions.WindowsClient); } else { @@ -78,9 +79,9 @@ getNamesAndValues(OSType).forEach(os => { settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - platformService.setup(p => p.isWindows).returns(() => os.value === OSType.Windows); - platformService.setup(p => p.isMac).returns(() => os.value === OSType.OSX); - platformService.setup(p => p.isLinux).returns(() => os.value === OSType.Linux); + platformService.setup(p => p.isWindows).returns(() => osType === OSType.Windows); + platformService.setup(p => p.isMac).returns(() => osType === OSType.OSX); + platformService.setup(p => p.isLinux).returns(() => osType === OSType.Linux); debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); debugProvider = new LaunchConfigurationResolver( @@ -409,7 +410,7 @@ getNamesAndValues(OSType).forEach(os => { ); const pathMappings = (debugConfig as LaunchRequestArguments).pathMappings; - const expected = os.name === 'Windows' + const expected = osType === OSType.Windows ? `c${localRoot.substring(1)}` : localRoot; expect(pathMappings).to.deep.equal([{ @@ -484,7 +485,7 @@ getNamesAndValues(OSType).forEach(os => { DebugOptions.ShowReturnValue, DebugOptions.RedirectOutput ]; - if (os.name === 'Windows') { + if (osType === OSType.Windows) { expectedOptions.push( DebugOptions.FixFilePathCase ); @@ -528,7 +529,7 @@ getNamesAndValues(OSType).forEach(os => { DebugOptions.DebugStdLib, DebugOptions.ShowReturnValue ]; - if (os.name === 'Windows') { + if (osType === OSType.Windows) { expectedOptions.push( DebugOptions.FixFilePathCase ); @@ -602,7 +603,7 @@ getNamesAndValues(OSType).forEach(os => { setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); - if (os.name === 'Windows') { + if (osType === OSType.Windows) { expect(debugConfig).to.have.property('debugOptions').contains(DebugOptions.FixFilePathCase); } else { expect(debugConfig).to.have.property('debugOptions').not.contains(DebugOptions.FixFilePathCase); From f07944c3c86eb1f5365f4f3d17ee62bfd47d64ec Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 14:27:42 -0600 Subject: [PATCH 13/22] Factor out getAvailableOptions(). --- .../resolvers/attach.unit.test.ts | 21 ++++++++++++------- .../resolvers/launch.unit.test.ts | 8 ------- 2 files changed, 13 insertions(+), 16 deletions(-) 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 8b3d66c886fd..47554c23f3e3 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -28,6 +28,18 @@ getNamesAndValues(OSType).forEach(os => { // So we can use path.join() even though the tests are OS-specific. // We could use hard-coded paths instead if we wanted to. + function getAvailableOptions(): string[] { + const options = [DebugOptions.RedirectOutput]; + if (osType === OSType.Windows) { + options.push(DebugOptions.FixFilePathCase); + options.push(DebugOptions.WindowsClient); + } else { + options.push(DebugOptions.UnixClient); + } + options.push(DebugOptions.ShowReturnValue); + return options; + } + suite(`Debugging - Config Resolver attach, OS = ${os.name}`, () => { let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; @@ -36,14 +48,7 @@ getNamesAndValues(OSType).forEach(os => { let documentManager: TypeMoq.IMock; let configurationService: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; - const debugOptionsAvailable = [DebugOptions.RedirectOutput]; - if (osType === OSType.Windows) { - debugOptionsAvailable.push(DebugOptions.FixFilePathCase); - debugOptionsAvailable.push(DebugOptions.WindowsClient); - } else { - debugOptionsAvailable.push(DebugOptions.UnixClient); - } - debugOptionsAvailable.push(DebugOptions.ShowReturnValue); + const debugOptionsAvailable = getAvailableOptions(); setup(() => { serviceContainer = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); 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 025c4abfed44..318cc71d457f 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -41,14 +41,6 @@ getNamesAndValues(OSType).forEach(os => { let documentManager: TypeMoq.IMock; let diagnosticsService: TypeMoq.IMock; let debugEnvHelper: TypeMoq.IMock; - const debugOptionsAvailable = [DebugOptions.RedirectOutput]; - if (osType === OSType.Windows) { - debugOptionsAvailable.push(DebugOptions.FixFilePathCase); - debugOptionsAvailable.push(DebugOptions.WindowsClient); - } else { - debugOptionsAvailable.push(DebugOptions.UnixClient); - } - debugOptionsAvailable.push(DebugOptions.ShowReturnValue); function createMoqWorkspaceFolder(folderPath: string) { const folder = TypeMoq.Mock.ofType(); folder.setup(f => f.uri).returns(() => Uri.file(folderPath)); From a997d3bddd7d97c12f7e3f19bac9cff3fc8a9a5c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 14:30:12 -0600 Subject: [PATCH 14/22] Factor out emulateOS(). --- .../resolvers/attach.unit.test.ts | 5 ++--- .../configuration/resolvers/common.ts | 20 +++++++++++++++++++ .../resolvers/launch.unit.test.ts | 5 ++--- 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 src/test/debugger/extension/configuration/resolvers/common.ts 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 47554c23f3e3..e0cf56836b21 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -18,6 +18,7 @@ import { OSType } from '../../../../../client/common/utils/platform'; import { AttachConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/attach'; import { AttachRequestArguments, DebugOptions } from '../../../../../client/debugger/types'; import { IServiceContainer } from '../../../../../client/ioc/types'; +import { emulateOS } from './common'; getNamesAndValues(OSType).forEach(os => { const osType: OSType = os.value as OSType; @@ -57,9 +58,7 @@ getNamesAndValues(OSType).forEach(os => { fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - platformService.setup(p => p.isWindows).returns(() => osType === OSType.Windows); - platformService.setup(p => p.isMac).returns(() => osType === OSType.OSX); - platformService.setup(p => p.isLinux).returns(() => osType === OSType.Linux); + emulateOS(os.value as OSType, platformService); documentManager = TypeMoq.Mock.ofType(); debugProvider = new AttachConfigurationResolver(workspaceService.object, documentManager.object, platformService.object, configurationService.object); }); diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts new file mode 100644 index 000000000000..580a6e108f55 --- /dev/null +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as TypeMoq from 'typemoq'; +import { IPlatformService } from '../../../../../client/common/platform/types'; +import { OSType } from '../../../../../client/common/utils/platform'; + +export function emulateOS( + osType: OSType, + platformService: TypeMoq.IMock +) { + platformService.setup(p => p.isWindows) + .returns(() => osType === OSType.Windows); + platformService.setup(p => p.isMac) + .returns(() => osType === OSType.OSX); + platformService.setup(p => p.isLinux) + .returns(() => osType === OSType.Linux); +} 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 318cc71d457f..79ec30ea91bd 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -22,6 +22,7 @@ import { IDebugEnvironmentVariablesService } from '../../../../../client/debugge import { LaunchConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/launch'; import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; +import { emulateOS } from './common'; getNamesAndValues(OSType).forEach(os => { const osType: OSType = os.value as OSType; @@ -71,9 +72,7 @@ getNamesAndValues(OSType).forEach(os => { settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - platformService.setup(p => p.isWindows).returns(() => osType === OSType.Windows); - platformService.setup(p => p.isMac).returns(() => osType === OSType.OSX); - platformService.setup(p => p.isLinux).returns(() => osType === OSType.Linux); + emulateOS(os.value as OSType, platformService); debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); debugProvider = new LaunchConfigurationResolver( From 5c354d880408112754cb362779a7ce6158371280 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 15:03:11 -0600 Subject: [PATCH 15/22] emulateOS() -> iterOSes(). --- .../resolvers/attach.unit.test.ts | 12 +++--- .../configuration/resolvers/common.ts | 38 ++++++++++++++----- .../resolvers/launch.unit.test.ts | 12 +++--- 3 files changed, 40 insertions(+), 22 deletions(-) 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 e0cf56836b21..5320a23ca9da 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -13,15 +13,13 @@ import { IDocumentManager, IWorkspaceService } from '../../../../../client/commo import { PYTHON_LANGUAGE } from '../../../../../client/common/constants'; import { IFileSystem, IPlatformService } from '../../../../../client/common/platform/types'; import { IConfigurationService } from '../../../../../client/common/types'; -import { getNamesAndValues } from '../../../../../client/common/utils/enum'; import { OSType } from '../../../../../client/common/utils/platform'; import { AttachConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/attach'; import { AttachRequestArguments, DebugOptions } from '../../../../../client/debugger/types'; import { IServiceContainer } from '../../../../../client/ioc/types'; -import { emulateOS } from './common'; +import { iterOSes } from './common'; -getNamesAndValues(OSType).forEach(os => { - const osType: OSType = os.value as OSType; +iterOSes().forEach(([osName, osType, setUpMocks]) => { if (osType === OSType.Unknown) { return; } @@ -41,7 +39,7 @@ getNamesAndValues(OSType).forEach(os => { return options; } - suite(`Debugging - Config Resolver attach, OS = ${os.name}`, () => { + suite(`Debugging - Config Resolver attach, OS = ${osName}`, () => { let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; @@ -58,7 +56,9 @@ getNamesAndValues(OSType).forEach(os => { fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - emulateOS(os.value as OSType, platformService); + setUpMocks( + platformService + ); documentManager = TypeMoq.Mock.ofType(); debugProvider = new AttachConfigurationResolver(workspaceService.object, documentManager.object, platformService.object, configurationService.object); }); diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index 580a6e108f55..e43d9ed60c85 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -5,16 +5,34 @@ import * as TypeMoq from 'typemoq'; import { IPlatformService } from '../../../../../client/common/platform/types'; +import { getNamesAndValues } from '../../../../../client/common/utils/enum'; import { OSType } from '../../../../../client/common/utils/platform'; -export function emulateOS( - osType: OSType, - platformService: TypeMoq.IMock -) { - platformService.setup(p => p.isWindows) - .returns(() => osType === OSType.Windows); - platformService.setup(p => p.isMac) - .returns(() => osType === OSType.OSX); - platformService.setup(p => p.isLinux) - .returns(() => osType === OSType.Linux); +export function iterOSes(): [ + string, // OS name + OSType, + // setUpMocks() + ( + platformService: TypeMoq.IMock + ) => void +][] { + return getNamesAndValues(OSType) + .map(os => { + const osType = os.value as OSType; + function setUpMocks( + platformService: TypeMoq.IMock + ) { + platformService.setup(p => p.isWindows) + .returns(() => osType === OSType.Windows); + platformService.setup(p => p.isMac) + .returns(() => osType === OSType.OSX); + platformService.setup(p => p.isLinux) + .returns(() => osType === OSType.Linux); + } + return [ + os.name, + osType, + setUpMocks + ]; + }); } 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 79ec30ea91bd..a91a2475b469 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -15,17 +15,15 @@ import { PYTHON_LANGUAGE } from '../../../../../client/common/constants'; import { IPlatformService } from '../../../../../client/common/platform/types'; import { IPythonExecutionFactory, IPythonExecutionService } from '../../../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../../../client/common/types'; -import { getNamesAndValues } from '../../../../../client/common/utils/enum'; import { OSType } from '../../../../../client/common/utils/platform'; import { DebuggerTypeName } from '../../../../../client/debugger/constants'; import { IDebugEnvironmentVariablesService } from '../../../../../client/debugger/extension/configuration/resolvers/helper'; import { LaunchConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/launch'; import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; -import { emulateOS } from './common'; +import { iterOSes } from './common'; -getNamesAndValues(OSType).forEach(os => { - const osType: OSType = os.value as OSType; +iterOSes().forEach(([osName, osType, setUpMocks]) => { if (osType === OSType.Unknown) { return; } @@ -33,7 +31,7 @@ getNamesAndValues(OSType).forEach(os => { // So we can use path.join() even though the tests are OS-specific. // We could use hard-coded paths instead if we wanted to. - suite(`Debugging - Config Resolver Launch, OS = ${os.name}`, () => { + suite(`Debugging - Config Resolver Launch, OS = ${osName}`, () => { let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; let pythonExecutionService: TypeMoq.IMock; @@ -72,7 +70,9 @@ getNamesAndValues(OSType).forEach(os => { settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - emulateOS(os.value as OSType, platformService); + setUpMocks( + platformService + ); debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); debugProvider = new LaunchConfigurationResolver( From 7014a17e2e4f939b7a79ddee94b6ad95c19c4d97 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 15:19:08 -0600 Subject: [PATCH 16/22] Move getOSType() to utils.platform. --- src/client/common/platform/platformService.ts | 26 +++++++++---------- src/client/common/utils/platform.ts | 13 ++++++++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/client/common/platform/platformService.ts b/src/client/common/platform/platformService.ts index 65c1623dd3eb..567f2d4bb831 100644 --- a/src/client/common/platform/platformService.ts +++ b/src/client/common/platform/platformService.ts @@ -7,7 +7,7 @@ import * as os from 'os'; import { coerce, SemVer } from 'semver'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName, PlatformErrors } from '../../telemetry/constants'; -import { OSType } from '../utils/platform'; +import { getOSType, OSType } from '../utils/platform'; import { parseVersion } from '../utils/version'; import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants'; import { IPlatformService } from './types'; @@ -16,6 +16,17 @@ import { IPlatformService } from './types'; export class PlatformService implements IPlatformService { public readonly osType: OSType = getOSType(); public version?: SemVer; + constructor() { + if (this.osType === OSType.Unknown) { + sendTelemetryEvent( + EventName.PLATFORM_INFO, + undefined, + { + failureType: PlatformErrors.FailedToDetermineOS + } + ); + } + } public get pathVariableName() { return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME; } @@ -66,16 +77,3 @@ export class PlatformService implements IPlatformService { return arch() === 'x64'; } } - -function getOSType(platform: string = process.platform): OSType { - if (/^win/.test(platform)) { - return OSType.Windows; - } else if (/^darwin/.test(platform)) { - return OSType.OSX; - } else if (/^linux/.test(platform)) { - return OSType.Linux; - } else { - sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { failureType: PlatformErrors.FailedToDetermineOS }); - return OSType.Unknown; - } -} diff --git a/src/client/common/utils/platform.ts b/src/client/common/utils/platform.ts index e293819b364f..770e15e0539e 100644 --- a/src/client/common/utils/platform.ts +++ b/src/client/common/utils/platform.ts @@ -14,3 +14,16 @@ export enum OSType { OSX = 'OSX', Linux = 'Linux' } + +// Return the OS type for the given platform string. +export function getOSType(platform: string = process.platform): OSType { + if (/^win/.test(platform)) { + return OSType.Windows; + } else if (/^darwin/.test(platform)) { + return OSType.OSX; + } else if (/^linux/.test(platform)) { + return OSType.Linux; + } else { + return OSType.Unknown; + } +} From 8fd026f63a7b2ee057d97766568ecdeeb9edaf8e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 21 Aug 2019 15:22:27 -0600 Subject: [PATCH 17/22] Make the "path" module match the test-specific target OS. --- .../resolvers/attach.unit.test.ts | 6 +---- .../configuration/resolvers/common.ts | 22 ++++++++++++++++++- .../resolvers/launch.unit.test.ts | 6 +---- 3 files changed, 23 insertions(+), 11 deletions(-) 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 5320a23ca9da..276279e695c1 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -6,7 +6,6 @@ // tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this 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'; @@ -19,13 +18,10 @@ import { AttachRequestArguments, DebugOptions } from '../../../../../client/debu import { IServiceContainer } from '../../../../../client/ioc/types'; import { iterOSes } from './common'; -iterOSes().forEach(([osName, osType, setUpMocks]) => { +iterOSes().forEach(([osName, osType, path, setUpMocks]) => { if (osType === OSType.Unknown) { return; } - // None of the behavior tested here relies on the path separator. - // So we can use path.join() even though the tests are OS-specific. - // We could use hard-coded paths instead if we wanted to. function getAvailableOptions(): string[] { const options = [DebugOptions.RedirectOutput]; diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index e43d9ed60c85..107613571cf8 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -3,14 +3,24 @@ 'use strict'; +import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { IPlatformService } from '../../../../../client/common/platform/types'; import { getNamesAndValues } from '../../../../../client/common/utils/enum'; -import { OSType } from '../../../../../client/common/utils/platform'; +import { getOSType, OSType } from '../../../../../client/common/utils/platform'; + +const OS_TYPE = getOSType(); + +interface IPathModule { + sep: string; + dirname(path: string): string; + join(...paths: string[]): string; +} export function iterOSes(): [ string, // OS name OSType, + IPathModule, // setUpMocks() ( platformService: TypeMoq.IMock @@ -19,6 +29,14 @@ export function iterOSes(): [ return getNamesAndValues(OSType) .map(os => { const osType = os.value as OSType; + + let pathMod: IPathModule = path; + if (osType !== OS_TYPE) { + pathMod = osType === OSType.Windows + ? path.win32 + : path.posix; + } + function setUpMocks( platformService: TypeMoq.IMock ) { @@ -29,9 +47,11 @@ export function iterOSes(): [ platformService.setup(p => p.isLinux) .returns(() => osType === OSType.Linux); } + return [ os.name, osType, + pathMod, setUpMocks ]; }); 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 a91a2475b469..bff3277e56e6 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -6,7 +6,6 @@ // tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion import { expect } from 'chai'; -import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; import { IInvalidPythonPathInDebuggerService } from '../../../../../client/application/diagnostics/types'; @@ -23,13 +22,10 @@ import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debu import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; import { iterOSes } from './common'; -iterOSes().forEach(([osName, osType, setUpMocks]) => { +iterOSes().forEach(([osName, osType, path, setUpMocks]) => { if (osType === OSType.Unknown) { return; } - // None of the behavior tested here relies on the path separator. - // So we can use path.join() even though the tests are OS-specific. - // We could use hard-coded paths instead if we wanted to. suite(`Debugging - Config Resolver Launch, OS = ${osName}`, () => { let debugProvider: DebugConfigurationProvider; From 63925fd9933722bb87549678cb0548830760a3dc Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 22 Aug 2019 15:10:05 -0600 Subject: [PATCH 18/22] Add comments for iterOSes(). --- .../extension/configuration/resolvers/common.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index 107613571cf8..9289ef781d7f 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -17,7 +17,9 @@ interface IPathModule { join(...paths: string[]): string; } -export function iterOSes(): [ +// The set of helpers, related to a target OS, that are available to +// tests. The target OS is not necessarily the native OS. +type OSTestHelpers = [ string, // OS name OSType, IPathModule, @@ -25,18 +27,27 @@ export function iterOSes(): [ ( platformService: TypeMoq.IMock ) => void -][] { +]; + +// For each supported OS, provide a set of helpers to use in tests. +export function iterOSes(): OSTestHelpers[] { return getNamesAndValues(OSType) .map(os => { const osType = os.value as OSType; + // Decide which "path" module to use. + // By default we use the regular module. let pathMod: IPathModule = path; if (osType !== OS_TYPE) { + // We are testing a different OS from the native one. + // So use a "path" module matching the target OS. pathMod = osType === OSType.Windows ? path.win32 : path.posix; } + // Generate the function to use for populating the + // relevant mocks relative to the target OS. function setUpMocks( platformService: TypeMoq.IMock ) { From 2d501d33a26e833ac8b247dd8445ab042465c6e0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 22 Aug 2019 15:17:21 -0600 Subject: [PATCH 19/22] Make OSTestHelpers an object. --- .../resolvers/attach.unit.test.ts | 8 +++--- .../configuration/resolvers/common.ts | 27 +++++++++---------- .../resolvers/launch.unit.test.ts | 8 +++--- 3 files changed, 23 insertions(+), 20 deletions(-) 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 276279e695c1..2c3637f7e4aa 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -18,10 +18,12 @@ import { AttachRequestArguments, DebugOptions } from '../../../../../client/debu import { IServiceContainer } from '../../../../../client/ioc/types'; import { iterOSes } from './common'; -iterOSes().forEach(([osName, osType, path, setUpMocks]) => { +iterOSes().forEach(helpers => { + const osType = helpers.osType; if (osType === OSType.Unknown) { return; } + const path = helpers.path; function getAvailableOptions(): string[] { const options = [DebugOptions.RedirectOutput]; @@ -35,7 +37,7 @@ iterOSes().forEach(([osName, osType, path, setUpMocks]) => { return options; } - suite(`Debugging - Config Resolver attach, OS = ${osName}`, () => { + suite(`Debugging - Config Resolver attach, OS = ${helpers.osName}`, () => { let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; @@ -52,7 +54,7 @@ iterOSes().forEach(([osName, osType, path, setUpMocks]) => { fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - setUpMocks( + helpers.setUpMocks( platformService ); documentManager = TypeMoq.Mock.ofType(); diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index 9289ef781d7f..c14b6b669696 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -19,15 +19,14 @@ interface IPathModule { // The set of helpers, related to a target OS, that are available to // tests. The target OS is not necessarily the native OS. -type OSTestHelpers = [ - string, // OS name - OSType, - IPathModule, - // setUpMocks() - ( +type OSTestHelpers = { + osName: string; + osType: OSType; + path: IPathModule; + setUpMocks( platformService: TypeMoq.IMock - ) => void -]; + ): void; +}; // For each supported OS, provide a set of helpers to use in tests. export function iterOSes(): OSTestHelpers[] { @@ -59,11 +58,11 @@ export function iterOSes(): OSTestHelpers[] { .returns(() => osType === OSType.Linux); } - return [ - os.name, - osType, - pathMod, - setUpMocks - ]; + return { + osName: os.name, + osType: osType, + path: pathMod, + setUpMocks: setUpMocks + }; }); } 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 bff3277e56e6..a5a69e0768ac 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -22,12 +22,14 @@ import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debu import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; import { iterOSes } from './common'; -iterOSes().forEach(([osName, osType, path, setUpMocks]) => { +iterOSes().forEach(helpers => { + const osType = helpers.osType; if (osType === OSType.Unknown) { return; } + const path = helpers.path; - suite(`Debugging - Config Resolver Launch, OS = ${osName}`, () => { + suite(`Debugging - Config Resolver Launch, OS = ${helpers.osName}`, () => { let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; let pythonExecutionService: TypeMoq.IMock; @@ -66,7 +68,7 @@ iterOSes().forEach(([osName, osType, path, setUpMocks]) => { settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - setUpMocks( + helpers.setUpMocks( platformService ); debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); From 4298d78fd07bbf9742e77c096bfb850eaaf6fb76 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 22 Aug 2019 15:32:01 -0600 Subject: [PATCH 20/22] iterOSes() => getHelpersPerOS(). --- .../extension/configuration/resolvers/attach.unit.test.ts | 4 ++-- src/test/debugger/extension/configuration/resolvers/common.ts | 2 +- .../extension/configuration/resolvers/launch.unit.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 2c3637f7e4aa..e0256ae3bf77 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -16,9 +16,9 @@ import { OSType } from '../../../../../client/common/utils/platform'; import { AttachConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/attach'; import { AttachRequestArguments, DebugOptions } from '../../../../../client/debugger/types'; import { IServiceContainer } from '../../../../../client/ioc/types'; -import { iterOSes } from './common'; +import { getHelpersPerOS } from './common'; -iterOSes().forEach(helpers => { +getHelpersPerOS().forEach(helpers => { const osType = helpers.osType; if (osType === OSType.Unknown) { return; diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index c14b6b669696..be05f40bcdab 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -29,7 +29,7 @@ type OSTestHelpers = { }; // For each supported OS, provide a set of helpers to use in tests. -export function iterOSes(): OSTestHelpers[] { +export function getHelpersPerOS(): OSTestHelpers[] { return getNamesAndValues(OSType) .map(os => { const osType = os.value as OSType; 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 a5a69e0768ac..de9340603f4d 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -20,9 +20,9 @@ import { IDebugEnvironmentVariablesService } from '../../../../../client/debugge import { LaunchConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/launch'; import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; -import { iterOSes } from './common'; +import { getHelpersPerOS } from './common'; -iterOSes().forEach(helpers => { +getHelpersPerOS().forEach(helpers => { const osType = helpers.osType; if (osType === OSType.Unknown) { return; From 24cc56c7cf74a37eec872313c18b1aa92218cd91 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 22 Aug 2019 15:40:59 -0600 Subject: [PATCH 21/22] Factor out setUpOSMocks(). --- .../resolvers/attach.unit.test.ts | 11 ++-- .../configuration/resolvers/common.ts | 57 +++++++++---------- .../resolvers/launch.unit.test.ts | 11 ++-- 3 files changed, 37 insertions(+), 42 deletions(-) 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 e0256ae3bf77..2d1d174ccd9a 100644 --- a/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts @@ -16,14 +16,12 @@ import { OSType } from '../../../../../client/common/utils/platform'; import { AttachConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/attach'; import { AttachRequestArguments, DebugOptions } from '../../../../../client/debugger/types'; import { IServiceContainer } from '../../../../../client/ioc/types'; -import { getHelpersPerOS } from './common'; +import { getInfoPerOS, setUpOSMocks } from './common'; -getHelpersPerOS().forEach(helpers => { - const osType = helpers.osType; +getInfoPerOS().forEach(([osName, osType, path]) => { if (osType === OSType.Unknown) { return; } - const path = helpers.path; function getAvailableOptions(): string[] { const options = [DebugOptions.RedirectOutput]; @@ -37,7 +35,7 @@ getHelpersPerOS().forEach(helpers => { return options; } - suite(`Debugging - Config Resolver attach, OS = ${helpers.osName}`, () => { + suite(`Debugging - Config Resolver attach, OS = ${osName}`, () => { let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; @@ -54,7 +52,8 @@ getHelpersPerOS().forEach(helpers => { fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - helpers.setUpMocks( + setUpOSMocks( + osType, platformService ); documentManager = TypeMoq.Mock.ofType(); diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index be05f40bcdab..e461e6c31edb 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -17,19 +17,16 @@ interface IPathModule { join(...paths: string[]): string; } -// The set of helpers, related to a target OS, that are available to -// tests. The target OS is not necessarily the native OS. -type OSTestHelpers = { - osName: string; - osType: OSType; - path: IPathModule; - setUpMocks( - platformService: TypeMoq.IMock - ): void; -}; +// The set of information, related to a target OS, that are available +// to tests. The target OS is not necessarily the native OS. +type OSTestInfo = [ + string, // os name + OSType, + IPathModule +]; // For each supported OS, provide a set of helpers to use in tests. -export function getHelpersPerOS(): OSTestHelpers[] { +export function getInfoPerOS(): OSTestInfo[] { return getNamesAndValues(OSType) .map(os => { const osType = os.value as OSType; @@ -45,24 +42,24 @@ export function getHelpersPerOS(): OSTestHelpers[] { : path.posix; } - // Generate the function to use for populating the - // relevant mocks relative to the target OS. - function setUpMocks( - platformService: TypeMoq.IMock - ) { - platformService.setup(p => p.isWindows) - .returns(() => osType === OSType.Windows); - platformService.setup(p => p.isMac) - .returns(() => osType === OSType.OSX); - platformService.setup(p => p.isLinux) - .returns(() => osType === OSType.Linux); - } - - return { - osName: os.name, - osType: osType, - path: pathMod, - setUpMocks: setUpMocks - }; + return [ + os.name, + osType, + pathMod + ]; }); } + +// Generate the function to use for populating the +// relevant mocks relative to the target OS. +export function setUpOSMocks( + osType: OSType, + platformService: TypeMoq.IMock +) { + platformService.setup(p => p.isWindows) + .returns(() => osType === OSType.Windows); + platformService.setup(p => p.isMac) + .returns(() => osType === OSType.OSX); + platformService.setup(p => p.isLinux) + .returns(() => osType === OSType.Linux); +} 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 de9340603f4d..13d324cd6dce 100644 --- a/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts +++ b/src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts @@ -20,16 +20,14 @@ import { IDebugEnvironmentVariablesService } from '../../../../../client/debugge import { LaunchConfigurationResolver } from '../../../../../client/debugger/extension/configuration/resolvers/launch'; import { DebugOptions, LaunchRequestArguments } from '../../../../../client/debugger/types'; import { IInterpreterHelper } from '../../../../../client/interpreter/contracts'; -import { getHelpersPerOS } from './common'; +import { getInfoPerOS, setUpOSMocks } from './common'; -getHelpersPerOS().forEach(helpers => { - const osType = helpers.osType; +getInfoPerOS().forEach(([osName, osType, path]) => { if (osType === OSType.Unknown) { return; } - const path = helpers.path; - suite(`Debugging - Config Resolver Launch, OS = ${helpers.osName}`, () => { + suite(`Debugging - Config Resolver Launch, OS = ${osName}`, () => { let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; let pythonExecutionService: TypeMoq.IMock; @@ -68,7 +66,8 @@ getHelpersPerOS().forEach(helpers => { settings.setup(s => s.envFile).returns(() => path.join(workspaceFolder!.uri.fsPath, '.env2')); } confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - helpers.setUpMocks( + setUpOSMocks( + osType, platformService ); debugEnvHelper.setup(x => x.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); From 241316a7a65105ed95af57e25737d3f40a276647 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 22 Aug 2019 15:45:07 -0600 Subject: [PATCH 22/22] Factor out getPathModuleForOS(). --- .../configuration/resolvers/common.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/test/debugger/extension/configuration/resolvers/common.ts b/src/test/debugger/extension/configuration/resolvers/common.ts index e461e6c31edb..59cf52c1679d 100644 --- a/src/test/debugger/extension/configuration/resolvers/common.ts +++ b/src/test/debugger/extension/configuration/resolvers/common.ts @@ -30,26 +30,28 @@ export function getInfoPerOS(): OSTestInfo[] { return getNamesAndValues(OSType) .map(os => { const osType = os.value as OSType; - - // Decide which "path" module to use. - // By default we use the regular module. - let pathMod: IPathModule = path; - if (osType !== OS_TYPE) { - // We are testing a different OS from the native one. - // So use a "path" module matching the target OS. - pathMod = osType === OSType.Windows - ? path.win32 - : path.posix; - } - return [ os.name, osType, - pathMod + getPathModuleForOS(osType) ]; }); } +// Decide which "path" module to use. +// By default we use the regular module. +function getPathModuleForOS(osType: OSType): IPathModule { + if (osType === OS_TYPE) { + return path; + } + + // We are testing a different OS from the native one. + // So use a "path" module matching the target OS. + return osType === OSType.Windows + ? path.win32 + : path.posix; +} + // Generate the function to use for populating the // relevant mocks relative to the target OS. export function setUpOSMocks(