Skip to content

Commit 5d2e848

Browse files
committed
enable debugging of pytests
1 parent 99e054f commit 5d2e848

3 files changed

Lines changed: 105 additions & 9 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
def main():
2+
import os
3+
import sys
4+
5+
sys.path[0] = os.getcwd()
6+
os.chdir(sys.argv[1])
7+
secret = sys.argv[2]
8+
port = int(sys.argv[3])
9+
testFx = sys.argv[4]
10+
args = sys.argv[5:]
11+
from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code
12+
from ptvsd.attach_server import DEFAULT_PORT, enable_attach, wait_for_attach
13+
14+
DONT_DEBUG.append(os.path.normcase(__file__))
15+
DEBUG_ENTRYPOINTS.add(get_code(main))
16+
17+
enable_attach(secret, ('127.0.0.1', port), redirect_output = False)
18+
sys.stdout.flush()
19+
print('READY')
20+
sys.stdout.flush()
21+
wait_for_attach()
22+
23+
try:
24+
if testFx == 'pytest':
25+
import pytest
26+
pytest.main(args)
27+
else:
28+
import nose
29+
nose.run(argv=args)
30+
sys.exit()
31+
finally:
32+
pass
33+
34+
if __name__ == '__main__':
35+
main()

src/client/unittests/pytest/main.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
2-
import {PythonSettings} from '../../common/configSettings';
3-
import {TestsToRun, Tests} from '../common/contracts';
4-
import {runTest} from './runner';
2+
import { PythonSettings } from '../../common/configSettings';
3+
import { TestsToRun, Tests } from '../common/contracts';
4+
import { runTest } from './runner';
55
import * as vscode from 'vscode';
6-
import {discoverTests} from './collector';
7-
import {BaseTestManager} from '../common/baseTestManager';
6+
import { discoverTests } from './collector';
7+
import { BaseTestManager } from '../common/baseTestManager';
88
import { Product } from '../../common/installer';
99

1010
const settings = PythonSettings.getInstance();
@@ -21,6 +21,6 @@ export class TestManager extends BaseTestManager {
2121
if (runFailedTests === true && args.indexOf('--lf') === -1 && args.indexOf('--last-failed') === -1) {
2222
args.push('--last-failed');
2323
}
24-
return runTest(this.rootDirectory, tests, args, testsToRun, this.cancellationToken, this.outputChannel);
24+
return runTest(this.rootDirectory, tests, args, testsToRun, this.cancellationToken, this.outputChannel, debug);
2525
}
2626
}

src/client/unittests/pytest/runner.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ import { CancellationToken, OutputChannel } from 'vscode';
88
import { updateResultsFromXmlLogFile, PassCalculationFormulae } from '../common/xUnitParser';
99
import { run } from '../common/runner';
1010
import { PythonSettings } from '../../common/configSettings';
11+
import * as vscode from 'vscode';
12+
import { execPythonFile } from './../../common/utils';
13+
import { createDeferred } from './../../common/helpers';
14+
import * as os from 'os';
15+
import * as path from 'path';
1116

1217
const pythonSettings = PythonSettings.getInstance();
1318

14-
export function runTest(rootDirectory: string, tests: Tests, args: string[], testsToRun?: TestsToRun, token?: CancellationToken, outChannel?: OutputChannel): Promise<Tests> {
19+
export function runTest(rootDirectory: string, tests: Tests, args: string[], testsToRun?: TestsToRun, token?: CancellationToken, outChannel?: OutputChannel, debug?: boolean): Promise<Tests> {
1520
let testPaths = [];
1621
if (testsToRun && testsToRun.testFolder) {
1722
testPaths = testPaths.concat(testsToRun.testFolder.map(f => f.nameToRun));
@@ -32,12 +37,68 @@ export function runTest(rootDirectory: string, tests: Tests, args: string[], tes
3237
return createTemporaryFile('.xml').then(xmlLogResult => {
3338
xmlLogFile = xmlLogResult.filePath;
3439
xmlLogFileCleanup = xmlLogResult.cleanupCallback;
35-
if (testPaths.length > 0){
40+
if (testPaths.length > 0) {
3641
// Ignore the test directories, as we're running a specific test
3742
args = args.filter(arg => arg.trim().startsWith('-'));
3843
}
3944
const testArgs = testPaths.concat(args, [`--junitxml=${xmlLogFile}`]);
40-
return run(pythonSettings.unitTest.pyTestPath, testArgs, rootDirectory, token, outChannel);
45+
if (debug) {
46+
const def = createDeferred<any>();
47+
const launchDef = createDeferred<any>();
48+
const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py');
49+
50+
// start the debug adapter only once we have started the debug process
51+
// pytestlauncherargs
52+
const pytestlauncherargs = [rootDirectory, 'my_secret', pythonSettings.unitTest.debugPort.toString(), 'pytest'];
53+
let outputChannelShown = false;
54+
execPythonFile(pythonSettings.pythonPath, [testLauncherFile].concat(pytestlauncherargs).concat(testArgs), rootDirectory, true, (data: string) => {
55+
if (data === 'READY' + os.EOL) {
56+
// debug socket server has started
57+
launchDef.resolve();
58+
}
59+
else {
60+
if (!outputChannelShown){
61+
outputChannelShown = true;
62+
outChannel.show();
63+
}
64+
outChannel.append(data);
65+
}
66+
}, token).catch(reason => {
67+
if (!def.rejected && !def.resolved) {
68+
def.reject(reason);
69+
}
70+
}).then(() => {
71+
if (!def.rejected && !def.resolved) {
72+
def.resolve();
73+
}
74+
}).catch(reason => {
75+
if (!def.rejected && !def.resolved) {
76+
def.reject(reason);
77+
}
78+
});
79+
80+
launchDef.promise.then(() => {
81+
return vscode.commands.executeCommand('vscode.startDebug', {
82+
"name": "Debug Unit Test",
83+
"type": "python",
84+
"request": "attach",
85+
"localRoot": rootDirectory,
86+
"remoteRoot": rootDirectory,
87+
"port": pythonSettings.unitTest.debugPort,
88+
"secret": "my_secret",
89+
"host": "localhost"
90+
});
91+
}).catch(reason => {
92+
if (!def.rejected && !def.resolved) {
93+
def.reject(reason);
94+
}
95+
});
96+
97+
return def.promise;
98+
}
99+
else {
100+
return run(pythonSettings.unitTest.pyTestPath, testArgs, rootDirectory, token, outChannel);
101+
}
41102
}).then(() => {
42103
return updateResultsFromLogFiles(tests, xmlLogFile);
43104
}).then(result => {

0 commit comments

Comments
 (0)