Skip to content
Merged
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
1 change: 1 addition & 0 deletions news/3 Code Health/983.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Launch the unit tests in debug mode as opposed to running and attaching the debugger.
15 changes: 3 additions & 12 deletions pythonFiles/PythonTools/testlauncher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,15 @@ def main():
import os
import sys
from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code
from ptvsd.attach_server import DEFAULT_PORT, enable_attach, wait_for_attach

sys.path[0] = os.getcwd()
os.chdir(sys.argv[1])
secret = sys.argv[2]
port = int(sys.argv[3])
testFx = sys.argv[4]
args = sys.argv[5:]
testFx = sys.argv[2]
args = sys.argv[3:]

DONT_DEBUG.append(os.path.normcase(__file__))
DEBUG_ENTRYPOINTS.add(get_code(main))

enable_attach(secret, ('127.0.0.1', port), redirect_output = False)
sys.stdout.flush()
print('READY')
sys.stdout.flush()
wait_for_attach()

try:
if testFx == 'pytest':
import pytest
Expand All @@ -32,4 +23,4 @@ def main():
pass

if __name__ == '__main__':
main()
main()
16 changes: 5 additions & 11 deletions pythonFiles/PythonTools/visualstudio_py_testlauncher.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,7 @@ def main():
global _channel

parser = OptionParser(prog = 'visualstudio_py_testlauncher', usage = 'Usage: %prog [<option>] <test names>... ')
parser.add_option('-s', '--secret', metavar='<secret>', help='restrict server to only allow clients that specify <secret> when connecting')
parser.add_option('-p', '--port', type='int', metavar='<port>', help='listen for debugger connections on <port>')
parser.add_option('--debug', action='store_true', help='Whether debugging the unit tests')
parser.add_option('-x', '--mixed-mode', action='store_true', help='wait for mixed-mode debugger to attach')
parser.add_option('-t', '--test', type='str', dest='tests', action='append', help='specifies a test to run')
parser.add_option('--testFile', type='str', help='Fully qualitified path to file name')
Expand All @@ -214,9 +213,8 @@ def main():
parser.add_option('--uc', '--catch', type='str', help='Catch control-C and display results')
(opts, _) = parser.parse_args()

if opts.secret and opts.port:
if opts.debug:
from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code
from ptvsd.attach_server import DEFAULT_PORT, enable_attach, wait_for_attach

sys.path[0] = os.getcwd()
if opts.result_port:
Expand All @@ -231,15 +229,11 @@ def main():
sys.stdout = _TestOutput(sys.stdout, is_stdout = True)
sys.stderr = _TestOutput(sys.stderr, is_stdout = False)

if opts.secret and opts.port:
if opts.debug:
DONT_DEBUG.append(os.path.normcase(__file__))
DEBUG_ENTRYPOINTS.add(get_code(main))

enable_attach(opts.secret, ('127.0.0.1', getattr(opts, 'port', DEFAULT_PORT)), redirect_output = True)
sys.stdout.flush()
print('READY')
sys.stdout.flush()
wait_for_attach()
pass
elif opts.mixed_mode:
# For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(),
# so we have to use Win32 API in a loop to do the same thing.
Expand Down Expand Up @@ -340,4 +334,4 @@ def main():
pass

if __name__ == '__main__':
main()
main()
103 changes: 23 additions & 80 deletions src/client/unittests/common/debugLauncher.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,33 @@
import * as getFreePort from 'get-port';
import { inject, injectable } from 'inversify';
import * as os from 'os';
import { injectable } from 'inversify';
import { debug, Uri, workspace } from 'vscode';
import { PythonSettings } from '../../common/configSettings';
import { IPythonExecutionFactory } from '../../common/process/types';
import { createDeferred } from './../../common/helpers';
import { ITestDebugLauncher, launchOptions } from './types';

const HAND_SHAKE = `READY${os.EOL}`;

@injectable()
export class DebugLauncher implements ITestDebugLauncher {
constructor( @inject(IPythonExecutionFactory) private pythonExecutionFactory: IPythonExecutionFactory) { }
public async getLaunchOptions(resource?: Uri): Promise<{ port: number, host: string }> {
const pythonSettings = PythonSettings.getInstance(resource);
const port = await getFreePort({ host: 'localhost', port: pythonSettings.unitTest.debugPort });
const host = typeof pythonSettings.unitTest.debugHost === 'string' && pythonSettings.unitTest.debugHost.trim().length > 0 ? pythonSettings.unitTest.debugHost.trim() : 'localhost';
return { port, host };
}
public async launchDebugger(options: launchOptions) {
if (options.token && options.token!.isCancellationRequested) {
return;
}
const cwdUri = options.cwd ? Uri.file(options.cwd) : undefined;
return this.pythonExecutionFactory.create(cwdUri)
.then(executionService => {
// tslint:disable-next-line:no-any
const def = createDeferred<void>();
// tslint:disable-next-line:no-any
const launchDef = createDeferred<void>();

let outputChannelShown = false;
let accumulatedData: string = '';
const result = executionService.execObservable(options.args, { cwd: options.cwd, mergeStdOutErr: true, token: options.token });
result.out.subscribe(output => {
let data = output.out;
if (!launchDef.resolved) {
accumulatedData += output.out;
if (!accumulatedData.startsWith(HAND_SHAKE)) {
return;
}
// Socket server has started, lets start the vs debugger.
launchDef.resolve();
data = accumulatedData.substring(HAND_SHAKE.length);
}

if (!outputChannelShown) {
outputChannelShown = true;
options.outChannel!.show();
}
options.outChannel!.append(data);
}, error => {
if (!def.completed) {
def.reject(error);
}
}, () => {
// Complete only when the process has completed.
if (!def.completed) {
def.resolve();
}
});

launchDef.promise
.then(() => {
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
throw new Error('Please open a workspace');
}
let workspaceFolder = workspace.getWorkspaceFolder(cwdUri!);
if (!workspaceFolder) {
workspaceFolder = workspace.workspaceFolders[0];
}
return debug.startDebugging(workspaceFolder, {
name: 'Debug Unit Test',
type: 'python',
request: 'attach',
localRoot: options.cwd,
remoteRoot: options.cwd,
port: options.port,
secret: 'my_secret',
host: options.host
});
})
.catch(reason => {
if (!def.completed) {
def.reject(reason);
}
});

return def.promise;
});
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
throw new Error('Please open a workspace');
}
let workspaceFolder = workspace.getWorkspaceFolder(cwdUri!);
if (!workspaceFolder) {
workspaceFolder = workspace.workspaceFolders[0];
}
const args = options.args.slice();
const program = args.shift();
return debug.startDebugging(workspaceFolder, {
name: 'Debug Unit Test',
type: 'python',
request: 'launch',
program,
cwd: cwdUri ? cwdUri.fsPath : workspaceFolder.uri.fsPath,
args,
console: 'none',
debugOptions: ['RedirectOutput']
}).then(() => void (0));
}
}
3 changes: 0 additions & 3 deletions src/client/unittests/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,11 @@ export type launchOptions = {
args: string[];
token?: CancellationToken;
outChannel?: OutputChannel;
port: number;
host: string;
};

export const ITestDebugLauncher = Symbol('ITestDebugLauncher');

export interface ITestDebugLauncher {
getLaunchOptions(resource?: Uri): Promise<{ port: number, host: string }>;
launchDebugger(options: launchOptions): Promise<void>;
}

Expand Down
15 changes: 6 additions & 9 deletions src/client/unittests/nosetest/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,12 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService:
return promiseToGetXmlLogFile.then(() => {
if (options.debug === true) {
const debugLauncher = serviceContainer.get<ITestDebugLauncher>(ITestDebugLauncher);
return debugLauncher.getLaunchOptions(options.workspaceFolder)
.then(debugPortAndHost => {
const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py');
const nosetestlauncherargs = [options.cwd, 'my_secret', debugPortAndHost.port.toString(), 'nose'];
const debuggerArgs = [testLauncherFile].concat(nosetestlauncherargs).concat(noseTestArgs.concat(testPaths));
const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, port: debugPortAndHost.port, host: debugPortAndHost.host };
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions) as Promise<any>;
});
const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py');
const nosetestlauncherargs = [options.cwd, 'nose'];
const debuggerArgs = [testLauncherFile].concat(nosetestlauncherargs).concat(noseTestArgs.concat(testPaths));
const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel };
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions) as Promise<any>;
} else {
// tslint:disable-next-line:prefer-type-cast no-any
const runOptions: Options = {
Expand Down
15 changes: 6 additions & 9 deletions src/client/unittests/pytest/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,12 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService:
const testArgs = testPaths.concat(args, [`--junitxml=${xmlLogFile}`]);
if (options.debug) {
const debugLauncher = serviceContainer.get<ITestDebugLauncher>(ITestDebugLauncher);
return debugLauncher.getLaunchOptions(options.workspaceFolder)
.then(debugPortAndHost => {
const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py');
const pytestlauncherargs = [options.cwd, 'my_secret', debugPortAndHost.port.toString(), 'pytest'];
const debuggerArgs = [testLauncherFile].concat(pytestlauncherargs).concat(testArgs);
const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, port: debugPortAndHost.port, host: debugPortAndHost.host };
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions) as Promise<any>;
});
const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py');
const pytestlauncherargs = [options.cwd, 'pytest'];
const debuggerArgs = [testLauncherFile].concat(pytestlauncherargs).concat(testArgs);
const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel };
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions) as Promise<any>;
} else {
const runOptions: Options = {
args: testArgs,
Expand Down
11 changes: 4 additions & 7 deletions src/client/unittests/unittest/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,10 @@ export async function runTest(serviceContainer: IServiceContainer, testManager:
}
if (options.debug === true) {
const debugLauncher = serviceContainer.get<ITestDebugLauncher>(ITestDebugLauncher);
return debugLauncher.getLaunchOptions(options.workspaceFolder)
.then(debugPortAndHost => {
testArgs.push(...['--secret=my_secret', `--port=${debugPortAndHost.port}`]);
const launchOptions = { cwd: options.cwd, args: [testLauncherFile].concat(testArgs), token: options.token, outChannel: options.outChannel, port: debugPortAndHost.port, host: debugPortAndHost.host };
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions);
});
testArgs.push(...['--debug']);
const launchOptions = { cwd: options.cwd, args: [testLauncherFile].concat(testArgs), token: options.token, outChannel: options.outChannel};
// tslint:disable-next-line:prefer-type-cast no-any
return debugLauncher.launchDebugger(launchOptions);
} else {
// tslint:disable-next-line:prefer-type-cast no-any
const runOptions: Options = {
Expand Down