diff --git a/news/2 Fixes/3568.md b/news/2 Fixes/3568.md new file mode 100644 index 000000000000..2342db710034 --- /dev/null +++ b/news/2 Fixes/3568.md @@ -0,0 +1 @@ +Using "request": "launch" item in launch.json for debugging sends pathMappings diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 4efb111655ba..bd596b9163ef 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -122,6 +122,19 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); }); }); + test('Ensure pathMappings property is correctly derived', 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, { localRoot: 'abc', remoteRoot: 'remoteabc' } as LaunchRequestArguments); + expect(debugConfig).to.have.property('pathMappings'); + expect(debugConfig!.pathMappings).to.deep.equal([{ localRoot: workspaceFolder.uri.fsPath, remoteRoot: '.' }, { localRoot: 'abc', remoteRoot: 'remoteabc' }]); + }); async function testFixFilePathCase(isWindows: boolean, isMac: boolean, isLinux: boolean) { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); diff --git a/src/test/testing/common/debugLauncher.unit.test.ts b/src/test/testing/common/debugLauncher.unit.test.ts index 90987785c0ce..471abaa2dfde 100644 --- a/src/test/testing/common/debugLauncher.unit.test.ts +++ b/src/test/testing/common/debugLauncher.unit.test.ts @@ -232,6 +232,19 @@ suite('Unit Tests - Debug Launcher', () => { if (isOs(OSType.Windows)) { expected.debugOptions.push(DebugOptions.FixFilePathCase); } + if (!expected.pathMappings) { + expected.pathMappings = expected.workspaceFolder ? [{ + localRoot: expected.workspaceFolder, + remoteRoot: '.' + }] : []; + } + // This is for backwards compatibility. + if (expected.localRoot && expected.remoteRoot) { + expected.pathMappings!.push({ + localRoot: expected.localRoot, + remoteRoot: expected.remoteRoot + }); + } setupDebugManager( workspaceFolders[0], @@ -240,177 +253,177 @@ suite('Unit Tests - Debug Launcher', () => { ); } - const testProviders: TestProvider[] = ['nosetest', 'pytest', 'unittest']; - // tslint:disable-next-line:max-func-body-length - testProviders.forEach(testProvider => { - const testTitleSuffix = `(Test Framework '${testProvider}')`; - - test(`Must launch debugger ${testTitleSuffix}`, async () => { - const options = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider - }; - setupSuccess(options, testProvider); - - await debugLauncher.launchDebugger(options); + const testProviders: TestProvider[] = ['nosetest', 'pytest', 'unittest']; + // tslint:disable-next-line:max-func-body-length + testProviders.forEach(testProvider => { + const testTitleSuffix = `(Test Framework '${testProvider}')`; - debugService.verifyAll(); - }); - test(`Must launch debugger with arguments ${testTitleSuffix}`, async () => { - const options = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py', '--debug', '1'], - testProvider - }; - setupSuccess(options, testProvider); - - await debugLauncher.launchDebugger(options); - - debugService.verifyAll(); - }); - test(`Must not launch debugger if cancelled ${testTitleSuffix}`, async () => { - debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => { - return Promise.resolve(undefined as any); - }) - .verifiable(TypeMoq.Times.never()); - - const cancellationToken = new CancellationTokenSource(); - cancellationToken.cancel(); - const token = cancellationToken.token; - const options: LaunchOptions = { cwd: '', args: [], token, testProvider }; - - await expect( - debugLauncher.launchDebugger(options) - ).to.be.eventually.equal(undefined, 'not undefined'); - - debugService.verifyAll(); - }); - test(`Must throw an exception if there are no workspaces ${testTitleSuffix}`, async () => { - hasWorkspaceFolders = false; - debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve(undefined as any)) - .verifiable(TypeMoq.Times.never()); - - const options: LaunchOptions = { cwd: '', args: [], testProvider }; - - await expect( - debugLauncher.launchDebugger(options) - ).to.eventually.rejectedWith('Please open a workspace'); - - debugService.verifyAll(); - }); - }); - - test('Tries launch.json first', async () => { - const options: LaunchOptions = { + test(`Must launch debugger ${testTitleSuffix}`, async () => { + const options = { cwd: 'one/two/three', args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' + testProvider }; - const expected = getDefaultDebugConfig(); - expected.name = 'spam'; - setupSuccess(options, 'unittest', expected, [ - { name: 'spam', type: DebuggerTypeName, request: 'test' } - ]); + setupSuccess(options, testProvider); await debugLauncher.launchDebugger(options); debugService.verifyAll(); }); - - test('Full debug config', async () => { - const options: LaunchOptions = { + test(`Must launch debugger with arguments ${testTitleSuffix}`, async () => { + const options = { cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = { - name: 'my tests', - type: DebuggerTypeName, - request: 'launch', - pythonPath: 'some/dir/bin/py3', - stopOnEntry: true, - showReturnValue: true, - console: 'integratedTerminal', - cwd: 'some/dir', - env: { - SPAM: 'EGGS' - }, - envFile: 'some/dir/.env', - redirectOutput: false, - debugStdLib: true, - justMyCode: false, - // added by LaunchConfigurationResolver: - internalConsoleOptions: 'neverOpen' + args: ['/one/two/three/testfile.py', '--debug', '1'], + testProvider }; - setupSuccess(options, 'unittest', expected, [ - { - name: 'my tests', - type: DebuggerTypeName, - request: 'test', - pythonPath: expected.pythonPath, - stopOnEntry: expected.stopOnEntry, - showReturnValue: expected.showReturnValue, - console: expected.console, - cwd: expected.cwd, - env: expected.env, - envFile: expected.envFile, - redirectOutput: expected.redirectOutput, - debugStdLib: expected.debugStdLib, - justMyCode: undefined - } - ]); + setupSuccess(options, testProvider); await debugLauncher.launchDebugger(options); debugService.verifyAll(); }); - - test('Uses first entry', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - expected.name = 'spam1'; - setupSuccess(options, 'unittest', expected, [ - { name: 'spam1', type: DebuggerTypeName, request: 'test' }, - { name: 'spam2', type: DebuggerTypeName, request: 'test' }, - { name: 'spam3', type: DebuggerTypeName, request: 'test' } - ]); - - await debugLauncher.launchDebugger(options); + test(`Must not launch debugger if cancelled ${testTitleSuffix}`, async () => { + debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => { + return Promise.resolve(undefined as any); + }) + .verifiable(TypeMoq.Times.never()); + + const cancellationToken = new CancellationTokenSource(); + cancellationToken.cancel(); + const token = cancellationToken.token; + const options: LaunchOptions = { cwd: '', args: [], token, testProvider }; + + await expect( + debugLauncher.launchDebugger(options) + ).to.be.eventually.equal(undefined, 'not undefined'); debugService.verifyAll(); }); + test(`Must throw an exception if there are no workspaces ${testTitleSuffix}`, async () => { + hasWorkspaceFolders = false; + debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined as any)) + .verifiable(TypeMoq.Times.never()); - test('Handles bad JSON', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - setupSuccess(options, 'unittest', expected, ']'); + const options: LaunchOptions = { cwd: '', args: [], testProvider }; - await debugLauncher.launchDebugger(options); + await expect( + debugLauncher.launchDebugger(options) + ).to.eventually.rejectedWith('Please open a workspace'); debugService.verifyAll(); }); + }); + + test('Tries launch.json first', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + expected.name = 'spam'; + setupSuccess(options, 'unittest', expected, [ + { name: 'spam', type: DebuggerTypeName, request: 'test' } + ]); + + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); + + test('Full debug config', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = { + name: 'my tests', + type: DebuggerTypeName, + request: 'launch', + pythonPath: 'some/dir/bin/py3', + stopOnEntry: true, + showReturnValue: true, + console: 'integratedTerminal', + cwd: 'some/dir', + env: { + SPAM: 'EGGS' + }, + envFile: 'some/dir/.env', + redirectOutput: false, + debugStdLib: true, + justMyCode: false, + // added by LaunchConfigurationResolver: + internalConsoleOptions: 'neverOpen' + }; + setupSuccess(options, 'unittest', expected, [ + { + name: 'my tests', + type: DebuggerTypeName, + request: 'test', + pythonPath: expected.pythonPath, + stopOnEntry: expected.stopOnEntry, + showReturnValue: expected.showReturnValue, + console: expected.console, + cwd: expected.cwd, + env: expected.env, + envFile: expected.envFile, + redirectOutput: expected.redirectOutput, + debugStdLib: expected.debugStdLib, + justMyCode: undefined + } + ]); + + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); + + test('Uses first entry', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + expected.name = 'spam1'; + setupSuccess(options, 'unittest', expected, [ + { name: 'spam1', type: DebuggerTypeName, request: 'test' }, + { name: 'spam2', type: DebuggerTypeName, request: 'test' }, + { name: 'spam3', type: DebuggerTypeName, request: 'test' } + ]); + + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); + + test('Handles bad JSON', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + setupSuccess(options, 'unittest', expected, ']'); - const malformedFiles = [ - '// test 1', - '// test 2 \n\ + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); + + const malformedFiles = [ + '// test 1', + '// test 2 \n\ { \n\ "name": "spam", \n\ "type": "python", \n\ "request": "test" \n\ } \n\ ', - '// test 3 \n\ + '// test 3 \n\ [ \n\ { \n\ "name": "spam", \n\ @@ -419,7 +432,7 @@ suite('Unit Tests - Debug Launcher', () => { } \n\ ] \n\ ', - '// test 4 \n\ + '// test 4 \n\ { \n\ "configurations": [ \n\ { \n\ @@ -430,129 +443,129 @@ suite('Unit Tests - Debug Launcher', () => { ] \n\ } \n\ ' - ]; - for (const text of malformedFiles) { - const testID = text.split('\n')[0].substring(3).trim(); - test(`Handles malformed launch.json - ${testID}`, async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - setupSuccess(options, 'unittest', expected, text); - - await debugLauncher.launchDebugger(options); - - debugService.verifyAll(); - }); - } - - test('Handles bad debug config items', async () => { + ]; + for (const text of malformedFiles) { + const testID = text.split('\n')[0].substring(3).trim(); + test(`Handles malformed launch.json - ${testID}`, async () => { const options: LaunchOptions = { cwd: 'one/two/three', args: ['/one/two/three/testfile.py'], testProvider: 'unittest' }; const expected = getDefaultDebugConfig(); - // tslint:disable:no-object-literal-type-assertion - setupSuccess(options, 'unittest', expected, [ - {} as DebugConfiguration, - { name: 'spam1' } as DebugConfiguration, - { name: 'spam2', type: DebuggerTypeName } as DebugConfiguration, - { name: 'spam3', request: 'test' } as DebugConfiguration, - { type: DebuggerTypeName } as DebugConfiguration, - { type: DebuggerTypeName, request: 'test' } as DebugConfiguration, - { request: 'test' } as DebugConfiguration - ]); - // tslint:enable:no-object-literal-type-assertion + setupSuccess(options, 'unittest', expected, text); await debugLauncher.launchDebugger(options); debugService.verifyAll(); }); + } - test('Handles non-python debug configs', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - setupSuccess(options, 'unittest', expected, [ - { name: 'foo', type: 'other', request: 'bar' } - ]); - - await debugLauncher.launchDebugger(options); + test('Handles bad debug config items', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + // tslint:disable:no-object-literal-type-assertion + setupSuccess(options, 'unittest', expected, [ + {} as DebugConfiguration, + { name: 'spam1' } as DebugConfiguration, + { name: 'spam2', type: DebuggerTypeName } as DebugConfiguration, + { name: 'spam3', request: 'test' } as DebugConfiguration, + { type: DebuggerTypeName } as DebugConfiguration, + { type: DebuggerTypeName, request: 'test' } as DebugConfiguration, + { request: 'test' } as DebugConfiguration + ]); + // tslint:enable:no-object-literal-type-assertion + + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); - debugService.verifyAll(); - }); + test('Handles non-python debug configs', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + setupSuccess(options, 'unittest', expected, [ + { name: 'foo', type: 'other', request: 'bar' } + ]); - test('Handles bogus python debug configs', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - setupSuccess(options, 'unittest', expected, [ - { name: 'spam', type: DebuggerTypeName, request: 'bogus' } - ]); + await debugLauncher.launchDebugger(options); - await debugLauncher.launchDebugger(options); + debugService.verifyAll(); + }); - debugService.verifyAll(); - }); + test('Handles bogus python debug configs', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + setupSuccess(options, 'unittest', expected, [ + { name: 'spam', type: DebuggerTypeName, request: 'bogus' } + ]); - test('Handles non-test debug config', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - setupSuccess(options, 'unittest', expected, [ - { name: 'spam', type: DebuggerTypeName, request: 'launch' }, - { name: 'spam', type: DebuggerTypeName, request: 'attach' } - ]); + await debugLauncher.launchDebugger(options); - await debugLauncher.launchDebugger(options); + debugService.verifyAll(); + }); - debugService.verifyAll(); - }); + test('Handles non-test debug config', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + setupSuccess(options, 'unittest', expected, [ + { name: 'spam', type: DebuggerTypeName, request: 'launch' }, + { name: 'spam', type: DebuggerTypeName, request: 'attach' } + ]); - test('Handles mixed debug config', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - expected.name = 'spam2'; - setupSuccess(options, 'unittest', expected, [ - { name: 'foo1', type: 'other', request: 'bar' }, - { name: 'foo2', type: 'other', request: 'bar' }, - { name: 'spam1', type: DebuggerTypeName, request: 'launch' }, - { name: 'spam2', type: DebuggerTypeName, request: 'test' }, - { name: 'spam3', type: DebuggerTypeName, request: 'attach' }, - { name: 'xyz', type: 'another', request: 'abc' } - ]); + await debugLauncher.launchDebugger(options); - await debugLauncher.launchDebugger(options); + debugService.verifyAll(); + }); - debugService.verifyAll(); - }); + test('Handles mixed debug config', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + expected.name = 'spam2'; + setupSuccess(options, 'unittest', expected, [ + { name: 'foo1', type: 'other', request: 'bar' }, + { name: 'foo2', type: 'other', request: 'bar' }, + { name: 'spam1', type: DebuggerTypeName, request: 'launch' }, + { name: 'spam2', type: DebuggerTypeName, request: 'test' }, + { name: 'spam3', type: DebuggerTypeName, request: 'attach' }, + { name: 'xyz', type: 'another', request: 'abc' } + ]); + + await debugLauncher.launchDebugger(options); + + debugService.verifyAll(); + }); - test('Handles comments', async () => { - const options: LaunchOptions = { - cwd: 'one/two/three', - args: ['/one/two/three/testfile.py'], - testProvider: 'unittest' - }; - const expected = getDefaultDebugConfig(); - expected.name = 'spam'; - expected.stopOnEntry = true; - setupSuccess(options, 'unittest', expected, ' \n\ + test('Handles comments', async () => { + const options: LaunchOptions = { + cwd: 'one/two/three', + args: ['/one/two/three/testfile.py'], + testProvider: 'unittest' + }; + const expected = getDefaultDebugConfig(); + expected.name = 'spam'; + expected.stopOnEntry = true; + setupSuccess(options, 'unittest', expected, ' \n\ { \n\ "version": "0.1.0", \n\ "configurations": [ \n\ @@ -569,10 +582,10 @@ suite('Unit Tests - Debug Launcher', () => { } \n\ '); - await debugLauncher.launchDebugger(options); + await debugLauncher.launchDebugger(options); - debugService.verifyAll(); - }); + debugService.verifyAll(); + }); test('Ensure trailing commands in JSON are handled', async () => { const workspaceFolder = { name: 'abc', index: 0, uri: Uri.file(__filename) }; const filename = path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json');