Skip to content

Commit 89a6801

Browse files
committed
added ability to debug unittests
1 parent 8003071 commit 89a6801

12 files changed

Lines changed: 174 additions & 57 deletions

File tree

package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@
3838
"onCommand:python.execInTerminal",
3939
"onCommand:python.sortImports",
4040
"onCommand:python.runtests",
41+
"onCommand:python.debugtests",
4142
"onCommand:python.setInterpreter",
4243
"onCommand:python.viewTestUI",
4344
"onCommand:python.viewTestOutput",
4445
"onCommand:python.selectAndRunTestMethod",
46+
"onCommand:python.selectAndDebugTestMethod",
4547
"onCommand:python.runFailedTests",
4648
"onCommand:python.execSelectionInTerminal",
4749
"onCommand:jupyter.runSelectionLine",
@@ -98,6 +100,11 @@
98100
"title": "Run All Unit Tests",
99101
"category": "Python"
100102
},
103+
{
104+
"command": "python.debugtests",
105+
"title": "Debug All Unit Tests",
106+
"category": "Python"
107+
},
101108
{
102109
"command": "python.execInTerminal",
103110
"title": "Run Python File in Terminal",
@@ -128,6 +135,11 @@
128135
"title": "Run Unit Test Method ...",
129136
"category": "Python"
130137
},
138+
{
139+
"command": "python.selectAndDebugTestMethod",
140+
"title": "Debug Unit Test Method ...",
141+
"category": "Python"
142+
},
131143
{
132144
"command": "python.runFailedTests",
133145
"title": "Run Failed Unit Tests",
@@ -206,6 +218,11 @@
206218
"command": "python.runtests",
207219
"group": "Python"
208220
},
221+
{
222+
"when": "resourceLangId == python",
223+
"command": "python.debugtests",
224+
"group": "Python"
225+
},
209226
{
210227
"when": "resourceLangId == python",
211228
"command": "python.execInTerminal",

pythonFiles/PythonTools/visualstudio_py_testlauncher.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ def main():
235235
DEBUG_ENTRYPOINTS.add(get_code(main))
236236

237237
enable_attach(opts.secret, ('127.0.0.1', getattr(opts, 'port', DEFAULT_PORT)), redirect_output = True)
238+
sys.stdout.flush()
239+
print('READY')
240+
sys.stdout.flush()
238241
wait_for_attach()
239242
elif opts.mixed_mode:
240243
# For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(),
@@ -312,7 +315,7 @@ def main():
312315
if opts.uf is not None:
313316
runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult, failfast=True)
314317
else:
315-
runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult)
318+
runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult)
316319
result = runner.run(tests)
317320
sys.exit(not result.wasSuccessful())
318321
finally:

src/client/common/constants.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ export namespace Commands {
77
export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal';
88
export const Tests_View_UI = 'python.viewTestUI';
99
export const Tests_Picker_UI = 'python.selectTestToRun';
10+
export const Tests_Picker_UI_Debug = 'python.selectTestToDebug';
1011
export const Tests_Discover = 'python.discoverTests';
1112
export const Tests_Run_Failed = 'python.runFailedTests';
1213
export const Sort_Imports = 'python.sortImports';
1314
export const Tests_Run = 'python.runtests';
15+
export const Tests_Debug = 'python.debugtests';
1416
export const Tests_Ask_To_Stop_Test = 'python.askToStopUnitTests';
1517
export const Tests_Ask_To_Stop_Discovery = 'python.askToStopUnitTestDiscovery';
1618
export const Tests_Stop = 'python.stopUnitTests';
1719
export const Tests_ViewOutput = 'python.viewTestOutput';
1820
export const Tests_Select_And_Run_Method = 'python.selectAndRunTestMethod';
21+
export const Tests_Select_And_Debug_Method = 'python.selectAndDebugTestMethod';
1922
export const Refactor_Extract_Variable = 'python.refactorExtractVariable';
2023
export const Refaactor_Extract_Method = 'python.refactorExtractMethod';
2124

@@ -53,7 +56,8 @@ export namespace Octicons {
5356
export const Button_Text_Tests_View_Output = 'View Output';
5457

5558
export namespace Text {
56-
export const CodeLensUnitTest = 'Test';
59+
export const CodeLensRunUnitTest = 'Run Test';
60+
export const CodeLensDebugUnitTest = 'Debug Test';
5761
}
5862
export namespace Delays {
5963
// Max time to wait before aborting the generation of code lenses for unit tests

src/client/unittests/codeLenses/testFiles.ts

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22

33
import * as vscode from 'vscode';
4-
import {CodeLensProvider, TextDocument, CancellationToken, CodeLens, SymbolInformation} from 'vscode';
5-
import {TestFile, TestsToRun, TestSuite, TestFunction} from '../common/contracts';
4+
import { CodeLensProvider, TextDocument, CancellationToken, CodeLens, SymbolInformation } from 'vscode';
5+
import { TestFile, TestsToRun, TestSuite, TestFunction } from '../common/contracts';
66
import * as constants from '../../common/constants';
7-
import {getDiscoveredTests} from '../common/testUtils';
7+
import { getDiscoveredTests } from '../common/testUtils';
88

99
interface CodeLensData {
1010
symbolKind: vscode.SymbolKind;
@@ -69,7 +69,7 @@ function getCodeLenses(documentUri: vscode.Uri, token: vscode.CancellationToken)
6969

7070
return getCodeLens(documentUri.fsPath, allFuncsAndSuites,
7171
range, symbol.name, symbol.kind);
72-
}).filter(codeLens => codeLens !== null);
72+
}).reduce((previous, current) => previous.concat(current), []).filter(codeLens => codeLens !== null);
7373
}, reason => {
7474
if (token.isCancellationRequested) {
7575
return [];
@@ -82,7 +82,7 @@ function getCodeLenses(documentUri: vscode.Uri, token: vscode.CancellationToken)
8282
const testParametirizedFunction = /.*\[.*\]/g;
8383

8484
function getCodeLens(fileName: string, allFuncsAndSuites: FunctionsAndSuites,
85-
range: vscode.Range, symbolName: string, symbolKind: vscode.SymbolKind): vscode.CodeLens {
85+
range: vscode.Range, symbolName: string, symbolKind: vscode.SymbolKind): vscode.CodeLens[] {
8686

8787
switch (symbolKind) {
8888
case vscode.SymbolKind.Function:
@@ -94,27 +94,41 @@ function getCodeLens(fileName: string, allFuncsAndSuites: FunctionsAndSuites,
9494
if (!cls) {
9595
return null;
9696
}
97-
return new CodeLens(range, {
98-
title: constants.Text.CodeLensUnitTest,
99-
command: constants.Commands.Tests_Run,
100-
arguments: [<TestsToRun>{ testSuite: [cls] }]
101-
});
97+
return [
98+
new CodeLens(range, {
99+
title: constants.Text.CodeLensRunUnitTest,
100+
command: constants.Commands.Tests_Run,
101+
arguments: [<TestsToRun>{ testSuite: [cls] }]
102+
}),
103+
new CodeLens(range, {
104+
title: constants.Text.CodeLensDebugUnitTest,
105+
command: constants.Commands.Tests_Debug,
106+
arguments: [<TestsToRun>{ testSuite: [cls] }]
107+
})
108+
];
102109
}
103110
}
104111

105112
return null;
106113
}
107114

108115
function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndSuites,
109-
symbolName: string, range: vscode.Range): vscode.CodeLens {
116+
symbolName: string, range: vscode.Range): vscode.CodeLens[] {
110117

111118
const fn = functionsAndSuites.functions.find(fn => fn.name === symbolName);
112119
if (fn) {
113-
return new CodeLens(range, {
114-
title: constants.Text.CodeLensUnitTest,
115-
command: constants.Commands.Tests_Run,
116-
arguments: [<TestsToRun>{ testFunction: [fn] }]
117-
});
120+
return [
121+
new CodeLens(range, {
122+
title: constants.Text.CodeLensRunUnitTest,
123+
command: constants.Commands.Tests_Run,
124+
arguments: [<TestsToRun>{ testFunction: [fn] }]
125+
}),
126+
new CodeLens(range, {
127+
title: constants.Text.CodeLensDebugUnitTest,
128+
command: constants.Commands.Tests_Debug,
129+
arguments: [<TestsToRun>{ testFunction: [fn] }]
130+
})
131+
];
118132
}
119133

120134
// Ok, possible we're dealing with parameterized unit tests
@@ -124,19 +138,33 @@ function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndS
124138
return null;
125139
}
126140
if (functions.length === 0) {
127-
return new CodeLens(range, {
128-
title: constants.Text.CodeLensUnitTest,
129-
command: constants.Commands.Tests_Run,
130-
arguments: [<TestsToRun>{ testFunction: functions }]
131-
});
141+
return [
142+
new CodeLens(range, {
143+
title: constants.Text.CodeLensRunUnitTest,
144+
command: constants.Commands.Tests_Run,
145+
arguments: [<TestsToRun>{ testFunction: functions }]
146+
}),
147+
new CodeLens(range, {
148+
title: constants.Text.CodeLensDebugUnitTest,
149+
command: constants.Commands.Tests_Debug,
150+
arguments: [<TestsToRun>{ testFunction: functions }]
151+
})
152+
];
132153
}
133154

134155
// Find all flattened functions
135-
return new CodeLens(range, {
136-
title: constants.Text.CodeLensUnitTest + ' (Multiple)',
137-
command: constants.Commands.Tests_Picker_UI,
138-
arguments: [filePath, functions]
139-
});
156+
return [
157+
new CodeLens(range, {
158+
title: constants.Text.CodeLensRunUnitTest + ' (Multiple)',
159+
command: constants.Commands.Tests_Picker_UI,
160+
arguments: [filePath, functions]
161+
}),
162+
new CodeLens(range, {
163+
title: constants.Text.CodeLensDebugUnitTest + ' (Multiple)',
164+
command: constants.Commands.Tests_Picker_UI_Debug,
165+
arguments: [filePath, functions]
166+
})
167+
];
140168
}
141169

142170
function getAllTestSuitesAndFunctionsPerFile(testFile: TestFile): FunctionsAndSuites {

src/client/unittests/common/baseTestManager.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ export abstract class BaseTestManager {
123123
return Promise.reject(reason);
124124
});
125125
}
126-
abstract discoverTestsImpl(ignoreCache: boolean): Promise<Tests>;
127-
public runTest(testsToRun?: TestsToRun): Promise<Tests>;
128-
public runTest(runFailedTests?: boolean): Promise<Tests>;
129-
public runTest(args: any): Promise<Tests> {
126+
abstract discoverTestsImpl(ignoreCache: boolean, debug?: boolean): Promise<Tests>;
127+
public runTest(testsToRun?: TestsToRun, debug?: boolean): Promise<Tests>;
128+
public runTest(runFailedTests?: boolean, debug?: boolean): Promise<Tests>;
129+
public runTest(args: any, debug?: boolean): Promise<Tests> {
130130
let runFailedTests = false;
131131
let testsToRun: TestsToRun = null;
132132
let moreInfo = {
@@ -177,7 +177,7 @@ export abstract class BaseTestManager {
177177
};
178178
})
179179
.then(tests => {
180-
return this.runTestImpl(tests, testsToRun, runFailedTests);
180+
return this.runTestImpl(tests, testsToRun, runFailedTests, debug);
181181
}).then(() => {
182182
this._status = TestStatus.Idle;
183183
this.disposeCancellationToken();
@@ -198,5 +198,5 @@ export abstract class BaseTestManager {
198198
return Promise.reject(reason);
199199
});
200200
}
201-
abstract runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean): Promise<any>;
201+
abstract runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any>;
202202
}

src/client/unittests/display/picker.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class TestDisplay {
3030
}, reject);
3131
});
3232
}
33-
public displayFunctionTestPickerUI(rootDirectory: string, fileName: string, testFunctions: TestFunction[]) {
33+
public displayFunctionTestPickerUI(rootDirectory: string, fileName: string, testFunctions: TestFunction[], debug?: boolean) {
3434
const tests = getDiscoveredTests();
3535
if (!tests) {
3636
return;
@@ -44,7 +44,10 @@ export class TestDisplay {
4444
testFunctions.some(testFunc => testFunc.nameToRun === fn.testFunction.nameToRun);
4545
});
4646

47-
window.showQuickPick(buildItemsForFunctions(rootDirectory, flattenedFunctions), { matchOnDescription: true, matchOnDetail: true }).then(onItemSelected);
47+
window.showQuickPick(buildItemsForFunctions(rootDirectory, flattenedFunctions),
48+
{ matchOnDescription: true, matchOnDetail: true }).then(testItem => {
49+
return onItemSelected(testItem, debug);
50+
});
4851
}
4952
}
5053

@@ -93,7 +96,7 @@ function getSummary(tests?: Tests) {
9396
function buildItems(rootDirectory: string, tests?: Tests): TestItem[] {
9497
const items: TestItem[] = [];
9598
items.push({ description: '', label: 'Run All Unit Tests', type: Type.RunAll });
96-
if (!tests || tests.testFiles.length === 0){
99+
if (!tests || tests.testFiles.length === 0) {
97100
items.push({ description: '', label: 'Discover Unit Tests', type: Type.ReDiscover });
98101
}
99102
items.push({ description: '', label: 'Run Unit Test Method ...', type: Type.SelectAndRunMethod });
@@ -147,7 +150,7 @@ function buildItemsForFunctions(rootDirectory: string, tests: FlattenedTestFunct
147150
});
148151
return functionItems;
149152
}
150-
function onItemSelected(selection: TestItem) {
153+
function onItemSelected(selection: TestItem, debug?: boolean) {
151154
if (!selection || typeof selection.type !== 'number') {
152155
return;
153156
}
@@ -174,7 +177,7 @@ function onItemSelected(selection: TestItem) {
174177
break;
175178
}
176179
case Type.SelectAndRunMethod: {
177-
cmd = constants.Commands.Tests_Select_And_Run_Method;
180+
cmd = debug ? constants.Commands.Tests_Select_And_Debug_Method : constants.Commands.Tests_Select_And_Run_Method;
178181
break;
179182
}
180183
case Type.RunMethod: {

src/client/unittests/main.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,16 @@ function registerCommands(): vscode.Disposable[] {
5959
}));
6060
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Failed, () => runTestsImpl(true)));
6161
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run, (testId) => runTestsImpl(testId)));
62+
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Debug, (testId) => runTestsImpl(testId, true)));
6263
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_View_UI, () => displayUI()));
6364
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI, (file, testFunctions) => displayPickerUI(file, testFunctions)));
65+
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI_Debug, (file, testFunctions) => displayPickerUI(file, testFunctions, true)));
6466
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Stop, () => stopTests()));
6567
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_ViewOutput, () => outChannel.show()));
6668
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Discovery, () => displayStopUI('Stop discovering tests')));
6769
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Test, () => displayStopUI('Stop running tests')));
6870
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_Method, () => selectAndRunTestMethod()));
71+
disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Debug_Method, () => selectAndRunTestMethod(true)));
6972

7073
return disposables;
7174
}
@@ -79,16 +82,16 @@ function displayUI() {
7982
testDisplay = testDisplay ? testDisplay : new TestDisplay();
8083
testDisplay.displayTestUI(vscode.workspace.rootPath);
8184
}
82-
function displayPickerUI(file: string, testFunctions: TestFunction[]) {
85+
function displayPickerUI(file: string, testFunctions: TestFunction[], debug?: boolean) {
8386
let testManager = getTestRunner();
8487
if (!testManager) {
8588
return displayTestFrameworkError(outChannel);
8689
}
8790

8891
testDisplay = testDisplay ? testDisplay : new TestDisplay();
89-
testDisplay.displayFunctionTestPickerUI(vscode.workspace.rootPath, file, testFunctions);
92+
testDisplay.displayFunctionTestPickerUI(vscode.workspace.rootPath, file, testFunctions, debug);
9093
}
91-
function selectAndRunTestMethod() {
94+
function selectAndRunTestMethod(debug?: boolean) {
9295
let testManager = getTestRunner();
9396
if (!testManager) {
9497
return displayTestFrameworkError(outChannel);
@@ -97,7 +100,7 @@ function selectAndRunTestMethod() {
97100
const tests = getDiscoveredTests();
98101
testDisplay = testDisplay ? testDisplay : new TestDisplay();
99102
testDisplay.selectTestFunction(vscode.workspace.rootPath, tests).then(testFn => {
100-
runTestsImpl(testFn);
103+
runTestsImpl(testFn, debug);
101104
}).catch(() => { });
102105
});
103106
}
@@ -223,7 +226,7 @@ function identifyTestType(rootDirectory: string, arg?: vscode.Uri | TestsToRun |
223226
}
224227
return null;
225228
}
226-
function runTestsImpl(arg?: vscode.Uri | TestsToRun | boolean | FlattenedTestFunction) {
229+
function runTestsImpl(arg?: vscode.Uri | TestsToRun | boolean | FlattenedTestFunction, debug: boolean = false) {
227230
let testManager = getTestRunner();
228231
if (!testManager) {
229232
return displayTestFrameworkError(outChannel);
@@ -234,7 +237,7 @@ function runTestsImpl(arg?: vscode.Uri | TestsToRun | boolean | FlattenedTestFun
234237

235238
testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(outChannel);
236239

237-
let runPromise = testManager.runTest(runInfo).catch(reason => {
240+
let runPromise = testManager.runTest(runInfo, debug).catch(reason => {
238241
if (reason !== CANCELLATION_REASON) {
239242
outChannel.appendLine('Error: ' + reason);
240243
}

src/client/unittests/nosetest/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class TestManager extends BaseTestManager {
1818
let args = settings.unitTest.nosetestArgs.slice(0);
1919
return discoverTests(this.rootDirectory, args, this.cancellationToken);
2020
}
21-
runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean): Promise<any> {
21+
runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any> {
2222
let args = settings.unitTest.nosetestArgs.slice(0);
2323
if (runFailedTests === true && args.indexOf('--failed') === -1) {
2424
args.push('--failed');

src/client/unittests/pytest/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class TestManager extends BaseTestManager {
1616
let args = settings.unitTest.pyTestArgs.slice(0);
1717
return discoverTests(this.rootDirectory, args, this.cancellationToken, ignoreCache);
1818
}
19-
runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean): Promise<any> {
19+
runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any> {
2020
let args = settings.unitTest.pyTestArgs.slice(0);
2121
if (runFailedTests === true && args.indexOf('--lf') === -1 && args.indexOf('--last-failed') === -1) {
2222
args.push('--last-failed');

0 commit comments

Comments
 (0)