Skip to content

Commit 6d776c7

Browse files
committed
Merge branch 'fixUnitTest'
* fixUnitTest: fixed issues in running unittests
2 parents e5702ef + 3e50a68 commit 6d776c7

File tree

8 files changed

+108
-29
lines changed

8 files changed

+108
-29
lines changed

pythonFiles/PythonTools/visualstudio_py_testlauncher.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def main():
203203
parser.add_option('-p', '--port', type='int', metavar='<port>', help='listen for debugger connections on <port>')
204204
parser.add_option('-x', '--mixed-mode', action='store_true', help='wait for mixed-mode debugger to attach')
205205
parser.add_option('-t', '--test', type='str', dest='tests', action='append', help='specifies a test to run')
206+
parser.add_option('--testFile', type='str', help='Fully qualitified path to file name')
206207
parser.add_option('-c', '--coverage', type='str', help='enable code coverage and specify filename')
207208
parser.add_option('-r', '--result-port', type='int', help='connect to port on localhost and send test results')
208209
parser.add_option('--us', type='str', help='Directory to start discovery')
@@ -264,14 +265,48 @@ def main():
264265
cov.start()
265266
except:
266267
pass
267-
if opts.tests is None:
268+
if opts.tests is None and opts.testFile is None:
268269
if opts.us is None:
269270
opts.us = '.'
270271
if opts.up is None:
271272
opts.up = 'test*.py'
272273
tests = unittest.defaultTestLoader.discover(opts.us, opts.up)
273274
else:
274-
tests = unittest.defaultTestLoader.loadTestsFromNames(opts.tests)
275+
# loadTestsFromNames doesn't work well (with duplicate file names or class names)
276+
# Easier approach is find the test suite and use that for running
277+
loader = unittest.TestLoader()
278+
# opts.us will be passed in
279+
suites = loader.discover(opts.us, pattern=os.path.basename(opts.testFile))
280+
suite = None
281+
tests = None
282+
if opts.tests is None:
283+
# Run everything in the test file
284+
tests = suites
285+
else:
286+
# Run a specific test class or test method
287+
for suite in suites._tests:
288+
for cls in suite._tests:
289+
try:
290+
for m in cls._tests:
291+
testId = m.id()
292+
if testId.startswith(opts.tests[0]):
293+
suite = cls
294+
if testId == opts.tests[0]:
295+
tests = m
296+
break
297+
except Exception as err:
298+
errorMessage = traceback.format_exception()
299+
pass
300+
if tests is None:
301+
tests = suite
302+
if tests is None and suite is None:
303+
_channel.send_event(
304+
name='error',
305+
outcome='',
306+
traceback = '',
307+
message = 'Failed to identify the test',
308+
test = ''
309+
)
275310
if opts.uvInt is None:
276311
opts.uvInt = 0
277312
runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult)

pythonFiles/preview/jedi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit b594a7d861af662fcb6487c9cada20dc7857e89b

src/client/unittests/nosetest/collector.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,9 @@ export function discoverTests(rootDirectory: string, args: string[], token: Canc
7777
});
7878
}
7979

80-
return execPythonFile(pythonSettings.unitTest.nosetestPath, args.concat(['--collect-only', '-vvv']), rootDirectory, true, processOutput)
81-
.then(() => {
82-
// process the last entry
83-
parseNoseTestModuleCollectionResult(rootDirectory, logOutputLines, testFiles);
80+
return execPythonFile(pythonSettings.unitTest.nosetestPath, args.concat(['--collect-only', '-vvv']), rootDirectory, true)
81+
.then(data => {
82+
processOutput(data);
8483
// Exclude tests that don't have any functions or test suites
8584
let indices = testFiles.filter(testFile => {
8685
return testFile.suites.length === 0 && testFile.functions.length === 0;

src/client/unittests/nosetest/runner.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22
import * as path from 'path';
3-
import {execPythonFile} from './../../common/utils';
43
import {createDeferred, createTemporaryFile} from '../../common/helpers';
54
import {OutputChannel, window, CancellationToken} from 'vscode';
65
import {TestFile, TestsToRun, TestSuite, TestFunction, FlattenedTestFunction, Tests, TestStatus, FlattenedTestSuite} from '../common/contracts';

src/client/unittests/pytest/collector.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,12 @@ export function discoverTests(rootDirectory: string, args: string[], token: vsco
8787
});
8888
}
8989

90-
return execPythonFile(pythonSettings.unitTest.pyTestPath, args.concat(['--collect-only']), rootDirectory, false, processOutput, token)
91-
.then(() => {
90+
return execPythonFile(pythonSettings.unitTest.pyTestPath, args.concat(['--collect-only']), rootDirectory, false, null, token)
91+
.then(data => {
92+
processOutput(data);
9293
if (token && token.isCancellationRequested) {
9394
return Promise.reject<Tests>('cancelled');
9495
}
95-
96-
// process the last entry
97-
parsePyTestModuleCollectionResult(rootDirectory, logOutputLines, testFiles, parentNodes);
9896
return flattenTestFiles(testFiles);
9997
});
10098
}

src/client/unittests/pytest/runner.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/// <reference path="../../../../typings/globals/xml2js/index.d.ts" />
22

33
'use strict';
4-
import {execPythonFile} from './../../common/utils';
54
import {createDeferred, createTemporaryFile} from '../../common/helpers';
65
import {TestFile, TestsToRun, TestSuite, TestFunction, FlattenedTestFunction, Tests, TestStatus, FlattenedTestSuite} from '../common/contracts';
76
import {extractBetweenDelimiters, flattenTestFiles, updateResults, convertFileToPackage} from '../common/testUtils';

src/client/unittests/unittest/collector.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ export function discoverTests(rootDirectory: string, args: string[], token: vsco
4343
const pythonScript = `import unittest
4444
loader = unittest.TestLoader()
4545
suites = loader.discover("${startDirectory}", pattern="${pattern}")
46-
print("start")
46+
print("start") #Don't remove this line
4747
for suite in suites._tests:
4848
for cls in suite._tests:
49-
for m in cls._tests:
50-
print(m.id())`;
49+
try:
50+
for m in cls._tests:
51+
print(m.id())
52+
except:
53+
pass`;
5154

5255
let startedCollecting = false;
5356
let testItems: string[] = [];
@@ -63,20 +66,25 @@ for suite in suites._tests:
6366
return;
6467
}
6568
line = line.trim();
66-
if (line.length === 0){
69+
if (line.length === 0) {
6770
return;
6871
}
6972
testItems.push(line);
7073
});
7174
}
7275
args = [];
73-
return execPythonFile(pythonSettings.pythonPath, args.concat(['-c', pythonScript]), rootDirectory, true, processOutput, token)
74-
.then(() => {
76+
return execPythonFile(pythonSettings.pythonPath, args.concat(['-c', pythonScript]), rootDirectory, true, null, token)
77+
.then(data => {
78+
processOutput(data);
7579
if (token && token.isCancellationRequested) {
7680
return Promise.reject<Tests>('cancelled');
7781
}
7882

79-
return parseTestIds(rootDirectory, testItems);
83+
let testsDirectory = rootDirectory;
84+
if (startDirectory.length > 1) {
85+
testsDirectory = path.isAbsolute(startDirectory) ? startDirectory : path.resolve(rootDirectory, startDirectory);
86+
}
87+
return parseTestIds(testsDirectory, testItems);
8088
});
8189
}
8290

@@ -109,7 +117,7 @@ function addTestId(rootDirectory: string, testId: string, testFiles: TestFile[])
109117
fullPath: filePath,
110118
functions: [],
111119
suites: [],
112-
nameToRun: paths.join('.'),
120+
nameToRun: `${className}.${functionName}`,
113121
xmlName: '',
114122
status: TestStatus.Idle,
115123
time: 0
@@ -118,7 +126,7 @@ function addTestId(rootDirectory: string, testId: string, testFiles: TestFile[])
118126
}
119127

120128
// Check if we already have this test file
121-
const classNameToRun = testIdParts.slice(0, testIdParts.length - 1).join('.');
129+
const classNameToRun = className;
122130
let testSuite = testFile.suites.find(cls => cls.nameToRun === classNameToRun);
123131
if (!testSuite) {
124132
testSuite = {

src/client/unittests/unittest/runner.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
'use strict';
44
import * as path from 'path';
5-
import {execPythonFile} from './../../common/utils';
65
import {createDeferred, createTemporaryFile} from '../../common/helpers';
76
import {TestFile, TestsToRun, TestSuite, TestFunction, FlattenedTestFunction, Tests, TestStatus, FlattenedTestSuite} from '../common/contracts';
87
import {extractBetweenDelimiters, flattenTestFiles, updateResults, convertFileToPackage} from '../common/testUtils';
@@ -66,18 +65,54 @@ export function runTest(rootDirectory: string, tests: Tests, args: string[], tes
6665
for (let counter = 0; counter < testPaths.length; counter++) {
6766
testPaths[counter] = '-t' + testPaths[counter].trim();
6867
}
69-
let testArgs = buildTestArgs(args);
70-
testArgs = [testLauncherFile].concat(testArgs).concat(`--result-port=${port}`).concat(testPaths);
71-
return run(settings.pythonPath, testArgs, rootDirectory, token, outChannel);
68+
const startTestDiscoveryDirectory = getStartDirectory(args);
69+
70+
function runTest(testFile: string = '', testId: string = '') {
71+
let testArgs = buildTestArgs(args);
72+
testArgs.push(`--result-port=${port}`);
73+
testArgs.push(`--us=${startTestDiscoveryDirectory}`);
74+
if (testId.length > 0) {
75+
testArgs.push(`-t${testId}`);
76+
}
77+
if (testFile.length > 0) {
78+
testArgs.push(`--testFile=${testFile}`);
79+
}
80+
return run(settings.pythonPath, [testLauncherFile].concat(testArgs), rootDirectory, token, outChannel);
81+
}
82+
83+
// Test everything
84+
if (testPaths.length === 0) {
85+
return runTest();
86+
}
87+
88+
// Ok, the ptvs test runner can only work with one test at a time
89+
let promise = Promise.resolve<string>('');
90+
if (Array.isArray(testsToRun.testFile)) {
91+
testsToRun.testFile.forEach(testFile => {
92+
promise = promise.then(() => runTest(testFile.fullPath, testFile.nameToRun));
93+
});
94+
}
95+
if (Array.isArray(testsToRun.testSuite)) {
96+
testsToRun.testSuite.forEach(testSuite => {
97+
const testFileName = tests.testSuits.find(t => t.testSuite === testSuite).parentTestFile.fullPath;
98+
promise = promise.then(() => runTest(testFileName, testSuite.nameToRun));
99+
});
100+
}
101+
if (Array.isArray(testsToRun.testFunction)) {
102+
testsToRun.testFunction.forEach(testFn => {
103+
const testFileName = tests.testFunctions.find(t => t.testFunction === testFn).parentTestFile.fullPath;
104+
promise = promise.then(() => runTest(testFileName, testFn.nameToRun));
105+
});
106+
}
107+
return promise;
72108
}).then(() => {
73109
updateResults(tests);
74110
return tests;
75111
});
76112
}
77113

78-
function buildTestArgs(args: string[]): string[] {
114+
function getStartDirectory(args: string[]): string {
79115
let startDirectory = '.';
80-
let pattern = 'test*.py';
81116
const indexOfStartDir = args.findIndex(arg => arg.indexOf('-s') === 0 || arg.indexOf('--start-directory') === 0);
82117
if (indexOfStartDir >= 0) {
83118
const startDir = args[indexOfStartDir].trim();
@@ -93,6 +128,11 @@ function buildTestArgs(args: string[]): string[] {
93128
}
94129
}
95130
}
131+
return startDirectory;
132+
}
133+
function buildTestArgs(args: string[]): string[] {
134+
const startTestDiscoveryDirectory = getStartDirectory(args);
135+
let pattern = 'test*.py';
96136
const indexOfPattern = args.findIndex(arg => arg.indexOf('-p') === 0 || arg.indexOf('--pattern') === 0);
97137
if (indexOfPattern >= 0) {
98138
const patternValue = args[indexOfPattern].trim();
@@ -110,7 +150,7 @@ function buildTestArgs(args: string[]): string[] {
110150
}
111151
const failFast = args.some(arg => arg.trim() === '-f' || arg.trim() === '--failfast');
112152
const verbosity = args.some(arg => arg.trim().indexOf('-v') === 0) ? 2 : 1;
113-
const testArgs = [`--us=${startDirectory}`, `--up=${pattern}`, `--uvInt=${verbosity}`];
153+
const testArgs = [`--us=${startTestDiscoveryDirectory}`, `--up=${pattern}`, `--uvInt=${verbosity}`];
114154
if (failFast) {
115155
testArgs.push('--uf');
116156
}

0 commit comments

Comments
 (0)