Skip to content

Commit 2355f37

Browse files
committed
more changes to unittests #239
1 parent 406846a commit 2355f37

File tree

12 files changed

+138
-195
lines changed

12 files changed

+138
-195
lines changed

pythonFiles/PythonTools/visualstudio_py_testlauncher.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def write(self, value):
5656
_channel.send_event('stdout' if self.is_stdout else 'stderr', content=value)
5757
if self.old_out:
5858
self.old_out.write(value)
59+
# flush immediately, else things go wonky and out of order
60+
self.flush()
5961

6062
def isatty(self):
6163
return True
@@ -133,7 +135,7 @@ def startTest(self, test):
133135

134136
def addError(self, test, err):
135137
super(VsTestResult, self).addError(test, err)
136-
self.sendResult(test, 'failed', err)
138+
self.sendResult(test, 'error', err)
137139

138140
def addFailure(self, test, err):
139141
super(VsTestResult, self).addFailure(test, err)
@@ -200,7 +202,7 @@ def main():
200202
parser.add_option('--us', type='str', help='Directory to start discovery')
201203
parser.add_option('--up', type='str', help='Pattern to match test files (''test*.py'' default)')
202204
parser.add_option('--ut', type='str', help='Top level directory of project (default to start directory)')
203-
parser.add_option('--uvInt', '--verboseInt', type='int', help='Verbose output')
205+
parser.add_option('--uvInt', '--verboseInt', type='int', help='Verbose output (0 none, 1 (no -v) simple, 2 (-v) full)')
204206
parser.add_option('--uf', '--failfast', type='str', help='Stop on first failure')
205207
parser.add_option('--uc', '--catch', type='str', help='Catch control-C and display results')
206208
(opts, _) = parser.parse_args()
@@ -265,7 +267,6 @@ def main():
265267
runner.failfast = 1
266268

267269
result = runner.run(tests)
268-
269270
sys.exit(not result.wasSuccessful())
270271
finally:
271272
if cov is not None:
@@ -277,6 +278,15 @@ def main():
277278
name='done'
278279
)
279280
_channel.socket.close()
281+
# prevent generation of the error 'Error in sys.exitfunc:'
282+
try:
283+
sys.stdout.close()
284+
except:
285+
pass
286+
try:
287+
sys.stderr.close()
288+
except:
289+
pass
280290

281291
if __name__ == '__main__':
282292
main()

src/client/common/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export namespace Commands {
99
export const Tests_Run_Failed = 'python.runFailedTests';
1010
export const Sort_Imports = 'python.sortImports';
1111
export const Tests_Run = 'python.runtests';
12+
export const Tests_Ask_To_Stop_Test = 'python.askToStopUnitTests';
13+
export const Tests_Ask_To_Stop_Discovery = 'python.askToStopUnitTestDiscovery';
1214
export const Tests_Stop = 'python.stopUnitTests';
1315
export const Tests_ViewOutput = 'python.viewTestOutput';
1416
export const Refactor_Extract_Variable = 'python.refactorExtractVariable';

src/client/unittests/codeLensProvider.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/client/unittests/codeLenses/testFiles.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function getCodeLens(fileName: string, allFuncsAndSuites: FunctionsAndSuites,
9898
return new CodeLens(range, {
9999
title: constants.Text.CodeLensUnitTest,
100100
command: constants.Commands.Tests_Run,
101-
arguments: [{ testSuite: [cls] }]
101+
arguments: [<TestsToRun>{ testSuite: [cls] }]
102102
});
103103
}
104104
}
@@ -114,7 +114,7 @@ function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndS
114114
return new CodeLens(range, {
115115
title: constants.Text.CodeLensUnitTest,
116116
command: constants.Commands.Tests_Run,
117-
arguments: [{ testFunction: [fn] }]
117+
arguments: [<TestsToRun>{ testFunction: [fn] }]
118118
});
119119
}
120120

@@ -128,7 +128,7 @@ function getFunctionCodeLens(filePath: string, functionsAndSuites: FunctionsAndS
128128
return new CodeLens(range, {
129129
title: constants.Text.CodeLensUnitTest,
130130
command: constants.Commands.Tests_Run,
131-
arguments: [{ testFunction: functions }]
131+
arguments: [<TestsToRun>{ testFunction: functions }]
132132
});
133133
}
134134

src/client/unittests/common/baseTestManager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export abstract class BaseTestManager {
7474
tests.testFiles.forEach(file => {
7575
if (file.errorsWhenDiscovering && file.errorsWhenDiscovering.length > 0) {
7676
haveErrorsInDiscovering = true;
77-
this.outputChannel.appendLine('');
7877
this.outputChannel.append('_'.repeat(10));
7978
this.outputChannel.append(`There was an error in identifying unit tests in ${file.nameToRun}`);
8079
this.outputChannel.appendLine('_'.repeat(10));

src/client/unittests/common/testUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@ export function storeDiscoveredTests(tests: Tests) {
2121
discoveredTests = tests;
2222
}
2323

24-
export function resolveValueAsTestToRun(name: string): TestsToRun {
24+
export function resolveValueAsTestToRun(name: string, rootDirectory: string): TestsToRun {
2525
// TODO: We need a better way to match (currently we have raw name, name, xmlname, etc = which one do we
2626
// use to identify a file given the full file name, similary for a folder and function
2727
// Perhaps something like a parser or methods like TestFunction.fromString()... something)
2828
let tests = getDiscoveredTests();
2929
if (!tests) { return null; }
30-
31-
let testFolders = tests.testFolders.filter(folder => folder.nameToRun === name || folder.name === name);
30+
const absolutePath = path.isAbsolute(name) ? name : path.resolve(rootDirectory, name);
31+
let testFolders = tests.testFolders.filter(folder => folder.nameToRun === name || folder.name === name || folder.name === absolutePath);
3232
if (testFolders.length > 0) { return { testFolder: testFolders }; };
3333

34-
let testFiles = tests.testFiles.filter(file => file.nameToRun === name || file.name === name || file.fullPath === name);
34+
let testFiles = tests.testFiles.filter(file => file.nameToRun === name || file.name === name || file.fullPath === absolutePath);
3535
if (testFiles.length > 0) { return { testFile: testFiles }; };
3636

3737
let testFns = tests.testFunctions.filter(fn => fn.testFunction.nameToRun === name || fn.testFunction.name === name).map(fn => fn.testFunction);

src/client/unittests/display/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class TestResultDisplay {
2424
}
2525
}
2626
public DisplayProgressStatus(tests: Promise<Tests>) {
27-
this.displayProgress('Running Tests', `Running Tests (Click to Stop)`, constants.Commands.Tests_Stop);
27+
this.displayProgress('Running Tests', `Running Tests (Click to Stop)`, constants.Commands.Tests_Ask_To_Stop_Test);
2828
tests
2929
.then(this.updateTestRunWithSuccess.bind(this))
3030
.catch(this.updateTestRunWithFailure.bind(this))
@@ -102,7 +102,7 @@ export class TestResultDisplay {
102102
}
103103

104104
public DisplayDiscoverStatus(tests: Promise<Tests>, quietMode: boolean = false) {
105-
this.displayProgress('Discovering Tests', 'Discovering Tests (Click to Stop)', constants.Commands.Tests_Stop);
105+
this.displayProgress('Discovering Tests', 'Discovering Tests (Click to Stop)', constants.Commands.Tests_Ask_To_Stop_Discovery);
106106
return tests.then(tests => {
107107
this.updateWithDiscoverSuccess(tests);
108108
return tests;

src/client/unittests/display/picker.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@ import * as vscode from 'vscode';
33
import {Tests, TestsToRun, TestFolder, TestFile, TestFunction, TestSuite, FlattenedTestFunction, TestStatus} from '../common/contracts';
44
import {getDiscoveredTests} from '../common/testUtils';
55
import * as constants from '../../common/constants';
6+
import * as path from 'path';
67

78
export class TestDisplay {
89
constructor() {
910
}
10-
public displayTestUI() {
11+
public displayStopTestUI(message: string) {
12+
window.showQuickPick([message]).then(item => {
13+
if (item === message) {
14+
vscode.commands.executeCommand(constants.Commands.Tests_Stop);
15+
}
16+
})
17+
}
18+
public displayTestUI(rootDirectory: string) {
1119
const tests = getDiscoveredTests();
12-
window.showQuickPick(buildItems(tests), { matchOnDescription: true, matchOnDetail: true }).then(onItemSelected);
20+
window.showQuickPick(buildItems(rootDirectory, tests), { matchOnDescription: true, matchOnDetail: true }).then(onItemSelected);
1321
}
14-
public displayFunctionTestPickerUI(fileName: string, testFunctions: TestFunction[]) {
22+
public displayFunctionTestPickerUI(rootDirectory: string, fileName: string, testFunctions: TestFunction[]) {
1523
const tests = getDiscoveredTests();
1624
if (!tests) {
1725
return;
@@ -25,7 +33,7 @@ export class TestDisplay {
2533
testFunctions.some(testFunc => testFunc.nameToRun === fn.testFunction.nameToRun);
2634
});
2735

28-
window.showQuickPick(buildItemsForFunctions(flattenedFunctions), { matchOnDescription: true, matchOnDetail: true }).then(onItemSelected);
36+
window.showQuickPick(buildItemsForFunctions(rootDirectory, flattenedFunctions), { matchOnDescription: true, matchOnDetail: true }).then(onItemSelected);
2937
}
3038
}
3139

@@ -37,7 +45,8 @@ enum Type {
3745
RunFile = 4,
3846
RunClass = 5,
3947
RunMethod = 6,
40-
ViewTestOutput = 7
48+
ViewTestOutput = 7,
49+
Null = 8
4150
}
4251
const statusIconMapping = new Map<TestStatus, string>();
4352
statusIconMapping.set(TestStatus.Pass, constants.Octicons.Test_Pass);
@@ -69,14 +78,20 @@ function getSummary(tests?: Tests) {
6978
}
7079
return statusText.join(', ').trim();
7180
}
72-
function buildItems(tests?: Tests): TestItem[] {
81+
function buildItems(rootDirectory: string, tests?: Tests): TestItem[] {
7382
const items: TestItem[] = [];
7483
items.push({ description: '', label: 'Run All Tests', type: Type.RunAll });
7584
items.push({ description: '', label: 'Rediscover Tests', type: Type.ReDiscover });
76-
const summary = getSummary(tests);
85+
86+
let summary = getSummary(tests);
87+
let separatorAdded = false;;
7788

7889
// Add an empty space because we'd like a separtor between actions and tests
79-
items.push({ description: '', label: 'View Test Output', type: Type.ViewTestOutput, detail: summary.length === 0 && tests ? ' ' : summary });
90+
if (summary.length === 0 && tests && tests.summary.failures === 0) {
91+
separatorAdded = true;
92+
summary = ' ';
93+
}
94+
items.push({ description: '', label: 'View Test Output', type: Type.ViewTestOutput, detail: summary });
8095

8196
if (!tests) {
8297
return items;
@@ -86,28 +101,49 @@ function buildItems(tests?: Tests): TestItem[] {
86101
items.push({ description: '', label: 'Run Failed Tests', type: Type.RunFailed, detail: `${constants.Octicons.Test_Fail} ${tests.summary.failures} Failed` });
87102
}
88103

89-
let functionItems = buildItemsForFunctions(tests.testFunctions);
104+
if (tests.testFunctions.length > 0 && !separatorAdded) {
105+
items.push({ description: '', label: '', type: Type.Null, detail: `` });
106+
}
107+
108+
let functionItems = buildItemsForFunctions(rootDirectory, tests.testFunctions, true, true);
90109
items.push(...functionItems);
91110
return items;
92111
}
93112

94-
function buildItemsForFunctions(tests: FlattenedTestFunction[]): TestItem[] {
113+
const statusSortPrefix = {};
114+
statusSortPrefix[TestStatus.Error] = '1';
115+
statusSortPrefix[TestStatus.Fail] = '2';
116+
statusSortPrefix[TestStatus.Skipped] = '3';
117+
statusSortPrefix[TestStatus.Pass] = '4';
118+
119+
function buildItemsForFunctions(rootDirectory: string, tests: FlattenedTestFunction[], sortBasedOnResults: boolean = false, displayStatusIcons: boolean = false): TestItem[] {
95120
let functionItems: TestItem[] = [];
96121
tests.forEach(fn => {
97122
const classPrefix = fn.parentTestSuite ? fn.parentTestSuite.name + '.' : '';
123+
let icon = '';
124+
if (displayStatusIcons && statusIconMapping.has(fn.testFunction.status)) {
125+
icon = `${statusIconMapping.get(fn.testFunction.status)} `;
126+
}
127+
98128
functionItems.push({
99129
description: '',
100-
detail: fn.parentTestFile.name,
101-
label: fn.testFunction.name,
130+
detail: path.relative(rootDirectory, fn.parentTestFile.fullPath),
131+
label: icon + fn.testFunction.name,
102132
type: Type.RunMethod,
103133
fn: fn
104134
});
105135
});
106136
functionItems.sort((a, b) => {
107-
if (a.detail + a.label < b.detail + b.label) {
137+
let sortAPrefix = '5-';
138+
let sortBPrefix = '5-';
139+
if (sortBasedOnResults) {
140+
sortAPrefix = statusSortPrefix[a.fn.testFunction.status] ? statusSortPrefix[a.fn.testFunction.status] : sortAPrefix;
141+
sortBPrefix = statusSortPrefix[b.fn.testFunction.status] ? statusSortPrefix[b.fn.testFunction.status] : sortBPrefix;
142+
}
143+
if (sortAPrefix + a.detail + a.label < sortBPrefix + b.detail + b.label) {
108144
return -1;
109145
}
110-
if (a.detail + a.label > b.detail + b.label) {
146+
if (sortAPrefix + a.detail + a.label > sortBPrefix + b.detail + b.label) {
111147
return 1;
112148
}
113149
return 0;
@@ -121,6 +157,9 @@ function onItemSelected(selection: TestItem) {
121157
let cmd = '';
122158
let args = [];
123159
switch (selection.type) {
160+
case Type.Null: {
161+
return;
162+
}
124163
case Type.RunAll: {
125164
cmd = constants.Commands.Tests_Run;
126165
break;

0 commit comments

Comments
 (0)