Skip to content

WIP - Add unit test to test debugging of modules (with -m) for experimental debugger #1276

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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"coverage": true
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"tslint.enable": true, // We will run our own linting in gulp (& git commit hooks), else tslint extension just complains about unmodified files
"tslint.enable": true,
"python.linting.enabled": false,
"python.unitTest.promptToConfigure": false,
"python.workspaceSymbols.enabled": false,
"python.formatting.provider": "none"
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@
}
},
{
"label": "%python.snippet.launch.attach.label%",
"label": "Python Experimental: Attach",
"description": "%python.snippet.launch.attach.description%",
"body": {
"name": "Attach (Remote Debug)",
Expand Down Expand Up @@ -1036,7 +1036,7 @@
{
"name": "Python Experimental: Attach",
"type": "pythonExperimental",
"request": "pythonExperimental",
"request": "attach",
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}",
"port": 3000,
Expand Down
200 changes: 200 additions & 0 deletions src/test/debugger/attach.again.ptvsd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

// tslint:disable:no-invalid-this max-func-body-length no-empty no-increment-decrement

import { expect } from 'chai';
import { ChildProcess, spawn } from 'child_process';
import * as getFreePort from 'get-port';
import * as path from 'path';
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 { sleep } from '../common';
import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize';
import { DEBUGGER_TIMEOUT } from './common/constants';
import { DebugClientEx } from './debugClient';

const fileToDebug = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger-start-with-ptvsd.re-attach.py');
const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd');
const DEBUG_ADAPTER = path.join(EXTENSION_ROOT_DIR, 'out', 'client', 'debugger', 'mainV2.js');

suite('Attach Debugger - detach and again again - Experimental', () => {
let debugClient1: DebugClient;
let debugClient2: DebugClient;
let procToKill: ChildProcess;
suiteSetup(initialize);

setup(async function () {
if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) {
this.skip();
}
});
teardown(async () => {
// Wait for a second before starting another test (sometimes, sockets take a while to get closed).
await sleep(1000);
try {
if (debugClient1) {
await debugClient1.disconnectRequest();
}
} catch (ex) { }
try {
if (debugClient2) {
await debugClient2.disconnectRequest();
}
} catch (ex) { }
if (procToKill) {
try {
procToKill.kill();
} catch { }
}
});
async function startDebugger() {
await sleep(1000);
const debugClient = createDebugAdapter();
debugClient.defaultTimeout = DEBUGGER_TIMEOUT;
await debugClient.start();
return debugClient;
}
/**
* Creates the debug aimport { AttachRequestArguments } from '../../client/debugger/Common/Contracts';
* We do not need to support code coverage on AppVeyor, lets use the standard test adapter.
* @returns {DebugClient}
*/
function createDebugAdapter(): DebugClient {
if (IS_WINDOWS) {
return new DebugClient('node', DEBUG_ADAPTER, 'pythonExperimental');
} else {
const coverageDirectory = path.join(EXTENSION_ROOT_DIR, 'debug_coverage_attach_ptvsd');
return new DebugClientEx(DEBUG_ADAPTER, 'pythonExperimental', coverageDirectory, { cwd: EXTENSION_ROOT_DIR });
}
}
async function startRemoteProcess() {
const port = await getFreePort({ host: 'localhost', port: 9091 });
const customEnv = { ...process.env };

// Set the path for PTVSD to be picked up.
// tslint:disable-next-line:no-string-literal
customEnv['PYTHONPATH'] = ptvsdPath;
const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()];
procToKill = spawn('python', pythonArgs, { env: customEnv, cwd: path.dirname(fileToDebug) });
// wait for socket server to start.
await sleep(1000);
return port;
}

async function waitForDebuggerCondfigurationDone(debugClient: DebugClient, port: number) {
// Send initialize, attach
const initializePromise = debugClient.initializeRequest({
adapterID: 'pythonExperimental',
linesStartAt1: true,
columnsStartAt1: true,
supportsRunInTerminalRequest: true,
pathFormat: 'path',
supportsVariableType: true,
supportsVariablePaging: true
});
const attachPromise = debugClient.attachRequest({
localRoot: path.dirname(fileToDebug),
remoteRoot: path.dirname(fileToDebug),
type: 'pythonExperimental',
port: port,
host: 'localhost',
logToFile: false,
debugOptions: ['RedirectOutput']
});

await Promise.all([
initializePromise,
attachPromise,
debugClient.waitForEvent('initialized')
]);

await debugClient.configurationDoneRequest();
}
async function testAttaching(debugClient: DebugClient, port: number) {
await waitForDebuggerCondfigurationDone(debugClient, port);
let threads = await debugClient.threadsRequest();
expect(threads).to.be.not.equal(undefined, 'no threads response');
expect(threads.body.threads).to.be.lengthOf(1);

await debugClient.setExceptionBreakpointsRequest({ filters: [] });
const breakpointLocation = { path: fileToDebug, column: 1, line: 7 };
await debugClient.setBreakpointsRequest({
lines: [breakpointLocation.line],
breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }],
source: { path: breakpointLocation.path }
});

await debugClient.assertStoppedLocation('breakpoint', breakpointLocation);
await debugClient.setBreakpointsRequest({ lines: [], breakpoints: [], source: { path: breakpointLocation.path } });

threads = await debugClient.threadsRequest();
expect(threads).to.be.not.equal(undefined, 'no threads response');
expect(threads.body.threads).to.be.lengthOf(1);

await debugClient.continueRequest({ threadId: threads.body.threads[0].id });
}

test('Confirm we are able to attach, detach and attach to a running program', async function () {
this.timeout(20000);
// Lets skip this test on AppVeyor (very flaky on AppVeyor).
if (IS_APPVEYOR) {
return;
}

let debugClient = debugClient1 = await startDebugger();

const port = await startRemoteProcess();
await testAttaching(debugClient, port);
await debugClient.disconnectRequest({});
debugClient = await startDebugger();
await testAttaching(debugClient, port);

const terminatedPromise = debugClient.waitForEvent('terminated');
procToKill.kill();
await terminatedPromise;
});

test('Confirm we are unable to attach if already attached to a running program', async function () {
this.timeout(200000);
// Lets skip this test on AppVeyor (very flaky on AppVeyor).
if (IS_APPVEYOR) {
return;
}

debugClient1 = await startDebugger();

const port = await startRemoteProcess();
await testAttaching(debugClient1, port);

debugClient2 = await startDebugger();
// Send initialize, attach
const initializePromise = debugClient2.initializeRequest({
adapterID: 'pythonExperimental',
linesStartAt1: true,
columnsStartAt1: true,
supportsRunInTerminalRequest: true,
pathFormat: 'path',
supportsVariableType: true,
supportsVariablePaging: true
});

const attachMustFail = 'A debugger is already attached to this process';
const attachPromise = debugClient2.attachRequest({
localRoot: path.dirname(fileToDebug),
remoteRoot: path.dirname(fileToDebug),
type: 'pythonExperimental',
port: port,
host: 'localhost',
logToFile: true,
debugOptions: ['RedirectOutput']
}).catch(() => Promise.resolve(attachMustFail));
// tslint:disable-next-line:no-unused-variable
const [_, attachResponse] = await Promise.all([initializePromise, attachPromise]);
expect(attachResponse).to.be.equal(attachMustFail);
});
});
6 changes: 1 addition & 5 deletions src/test/debugger/misc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ let testCounter = 0;
// tslint:disable-next-line:no-string-literal
env['PYTHONPATH'] = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd');
}
// tslint:disable-next-line:no-unnecessary-local-variable
const options: LaunchRequestArguments = {
program: path.join(debugFilesPath, pythonFile),
cwd: debugFilesPath,
Expand All @@ -84,11 +85,6 @@ let testCounter = 0;
type: debuggerType
};

// Custom experimental debugger options (filled in by DebugConfigurationProvider).
if (debuggerType === 'pythonExperimental') {
(options as any).redirectOutput = true;
}

return options;
}

Expand Down
69 changes: 69 additions & 0 deletions src/test/debugger/module.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

// tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any

import * as path from 'path';
import { DebugClient } from 'vscode-debugadapter-testsupport';
import { EXTENSION_ROOT_DIR } from '../../client/common/constants';
import { noop } from '../../client/common/core.utils';
import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts';
import { sleep } from '../common';
import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize';
import { createDebugAdapter } from './utils';

const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5');
const debuggerType = 'pythonExperimental';
suite(`Module Debugging - Misc tests: ${debuggerType}`, () => {
let debugClient: DebugClient;
setup(async function () {
if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) {
this.skip();
}
const coverageDirectory = path.join(EXTENSION_ROOT_DIR, 'debug_coverage_module');
debugClient = await createDebugAdapter(coverageDirectory);
});
teardown(async () => {
// Wait for a second before starting another test (sometimes, sockets take a while to get closed).
await sleep(1000);
try {
await debugClient.stop().catch(noop);
// tslint:disable-next-line:no-empty
} catch (ex) { }
await sleep(1000);
});
function buildLauncArgs(): LaunchRequestArguments {
const env = {};
// tslint:disable-next-line:no-string-literal
env['PYTHONPATH'] = `.${path.delimiter}${path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd')}`;

// tslint:disable-next-line:no-unnecessary-local-variable
const options: LaunchRequestArguments = {
module: 'mymod',
program: '',
cwd: workspaceDirectory,
debugOptions: [DebugOptions.RedirectOutput],
pythonPath: 'python',
args: [],
env,
envFile: '',
logToFile: false,
type: debuggerType
};

return options;
}

test('Test stdout output', async () => {
await Promise.all([
debugClient.configurationSequence(),
debugClient.launch(buildLauncArgs()),
debugClient.waitForEvent('initialized'),
debugClient.assertOutput('stdout', 'hello world'),
debugClient.waitForEvent('exited'),
debugClient.waitForEvent('terminated')
]);
});
});
Empty file.
1 change: 1 addition & 0 deletions src/testMultiRootWkspc/workspace5/mymod/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print("hello world")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import sys
import time
# Give the debugger some time to add a breakpoint.
time.sleep(5)
for i in range(10000):
time.sleep(0.5)
pass

print('bye')