Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mapping of local and remote paths in remote debugging #1300

Merged
merged 10 commits into from
Apr 6, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,31 @@
},
"default": []
},
"pathMappings": {
"type": "array",
"label": "Additional path mappings.",
"items": {
"type": "object",
"label": "Path mapping",
"required": [
"localRoot",
"remoteRoot"
],
"properties": {
"localRoot": {
"type": "string",
"label": "Local source root.",
"default": ""
},
"remoteRoot": {
"type": "string",
"label": "Remote source root.",
"default": ""
}
}
},
"default": []
},
"logToFile": {
"type": "boolean",
"description": "Enable logging of debugger events to a log file.",
Expand Down
2 changes: 2 additions & 0 deletions src/client/debugger/Common/Contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum
host?: string;
secret?: string;
logToFile?: boolean;
pathMappings?: { localRoot: string; remoteRoot: string }[];
debugOptions?: DebugOptions[];
}

export interface IDebugServer {
Expand Down
8 changes: 8 additions & 0 deletions src/client/debugger/configProviders/pythonV2Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,13 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide
if (this.serviceContainer.get<IPlatformService>(IPlatformService).isWindows) {
debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase);
}

if (!debugConfiguration.pathMappings) {
debugConfiguration.pathMappings = [];
}
debugConfiguration.pathMappings!.push({
localRoot: debugConfiguration.localRoot,
remoteRoot: debugConfiguration.remoteRoot
});
}
}
10 changes: 8 additions & 2 deletions src/test/autocomplete/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ const fileEncodingUsed = path.join(autoCompPath, 'five.py');
const fileSuppress = path.join(autoCompPath, 'suppress.py');

// tslint:disable-next-line:max-func-body-length
suite('Autocomplete', () => {
suite('Autocomplete', function () {
// Attempt to fix #1301
// tslint:disable-next-line:no-invalid-this
this.timeout(60000);
let isPython2: boolean;
let ioc: UnitTestIocContainer;

suiteSetup(async () => {
suiteSetup(async function () {
// Attempt to fix #1301
// tslint:disable-next-line:no-invalid-this
this.timeout(60000);
await initialize();
initializeDI();
isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2;
Expand Down
60 changes: 47 additions & 13 deletions src/test/debugger/attach.ptvsd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@
import { ChildProcess, spawn } from 'child_process';
import * as getFreePort from 'get-port';
import * as path from 'path';
import * as TypeMoq from 'typemoq';
import { DebugConfiguration, Uri } from 'vscode';
import { DebugClient } from 'vscode-debugadapter-testsupport';
import { EXTENSION_ROOT_DIR } from '../../client/common/constants';
import '../../client/common/extensions';
import { IS_WINDOWS } from '../../client/common/platform/constants';
import { PlatformService } from '../../client/common/platform/platformService';
import { IPlatformService } from '../../client/common/platform/types';
import { PythonV2DebugConfigurationProvider } from '../../client/debugger';
import { PTVSD_PATH } from '../../client/debugger/Common/constants';
import { DebugOptions } from '../../client/debugger/Common/Contracts';
import { AttachRequestArguments, DebugOptions } from '../../client/debugger/Common/Contracts';
import { IServiceContainer } from '../../client/ioc/types';
import { sleep } from '../common';
import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize';
import { continueDebugging, createDebugAdapter } from './utils';
Expand Down Expand Up @@ -43,13 +50,7 @@ suite('Attach Debugger - Experimental', () => {
} catch { }
}
});
test('Confirm we are able to attach to a running program', async function () {
this.timeout(20000);
// Lets skip this test on AppVeyor (very flaky on AppVeyor).
if (IS_APPVEYOR) {
return;
}

async function testAttachingToRemoteProcess(localRoot: string, remoteRoot: string, pathSeparator: string) {
const port = await getFreePort({ host: 'localhost', port: 3000 });
const customEnv = { ...process.env };

Expand All @@ -58,6 +59,8 @@ suite('Attach Debugger - Experimental', () => {
customEnv['PYTHONPATH'] = PTVSD_PATH;
const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()];
procToKill = spawn('python', pythonArgs, { env: customEnv, cwd: path.dirname(fileToDebug) });
// wait for remote socket to start
await sleep(1000);

// Send initialize, attach
const initializePromise = debugClient.initializeRequest({
Expand All @@ -69,15 +72,23 @@ suite('Attach Debugger - Experimental', () => {
supportsVariableType: true,
supportsVariablePaging: true
});
const attachPromise = debugClient.attachRequest({
localRoot: path.dirname(fileToDebug),
remoteRoot: path.dirname(fileToDebug),
const options: AttachRequestArguments & DebugConfiguration = {
name: 'attach',
request: 'attach',
localRoot,
remoteRoot,
type: 'pythonExperimental',
port: port,
host: 'localhost',
logToFile: false,
debugOptions: [DebugOptions.RedirectOutput]
});
};
const serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
serviceContainer.setup(c => c.get(IPlatformService, TypeMoq.It.isAny())).returns(() => new PlatformService());
const configProvider = new PythonV2DebugConfigurationProvider(serviceContainer.object);

await configProvider.resolveDebugConfiguration({ index: 0, name: 'root', uri: Uri.file(localRoot) }, options);
const attachPromise = debugClient.attachRequest(options);

await Promise.all([
initializePromise,
Expand All @@ -90,7 +101,9 @@ suite('Attach Debugger - Experimental', () => {
const stdOutPromise = debugClient.assertOutput('stdout', 'this is stdout');
const stdErrPromise = debugClient.assertOutput('stderr', 'this is stderr');

const breakpointLocation = { path: fileToDebug, column: 1, line: 12 };
// Don't use path utils, as we're building the paths manually (mimic windows paths on unix test servers and vice versa).
const localFileName = `${localRoot}${pathSeparator}${path.basename(fileToDebug)}`;
const breakpointLocation = { path: localFileName, column: 1, line: 12 };
const breakpointPromise = debugClient.setBreakpointsRequest({
lines: [breakpointLocation.line],
breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }],
Expand All @@ -111,5 +124,26 @@ suite('Attach Debugger - Experimental', () => {
debugClient.waitForEvent('exited'),
debugClient.waitForEvent('terminated')
]);
}
test('Confirm we are able to attach to a running program', async function () {
this.timeout(20000);
// Lets skip this test on AppVeyor (very flaky on AppVeyor).
if (IS_APPVEYOR) {
return;
}

await testAttachingToRemoteProcess(path.dirname(fileToDebug), path.dirname(fileToDebug), path.sep);
});
test('Confirm local and remote paths are translated', async function () {
this.timeout(20000);
// Lets skip this test on AppVeyor (very flaky on AppVeyor).
if (IS_APPVEYOR) {
return;
}

// If tests are running on windows, then treat debug client as a unix client and remote process as current OS.
const localWorkspace = IS_WINDOWS ? '/home/user/Desktop/project/src' : 'C:\\Project\\src';
const pathSeparator = IS_WINDOWS ? '/' : '\\';
await testAttachingToRemoteProcess(localWorkspace, path.dirname(fileToDebug), pathSeparator);
});
});
14 changes: 10 additions & 4 deletions src/test/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ export async function initializeTest(): Promise<any> {
// Dispose any cached python settings (used only in test env).
PythonSettings.dispose();
}

export async function closeActiveWindows(): Promise<void> {
return new Promise<void>((resolve, reject) => vscode.commands.executeCommand('workbench.action.closeAllEditors')
// tslint:disable-next-line:no-unnecessary-callback-wrapper
.then(() => resolve(), reject));
return new Promise<void>((resolve, reject) => {
vscode.commands.executeCommand('workbench.action.closeAllEditors')
// tslint:disable-next-line:no-unnecessary-callback-wrapper
.then(() => resolve(), reject);
// Attempt to fix #1301.
// Lets not waste too much time.
setTimeout(() => {
reject(new Error('Command \'workbench.action.closeAllEditors\' timedout'));
}, 15000);
});
}

function getPythonPath(): string {
Expand Down