Skip to content

Update pytest parser to handle pytest 4.1 #4098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 23, 2019
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/2 Fixes/4099.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle stdout changes with updates to pytest 4.1.x series (without breaking 4.0.x series parsing).
101 changes: 58 additions & 43 deletions src/client/unittests/pytest/services/parserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import { inject, injectable } from 'inversify';
import * as os from 'os';
import * as path from 'path';
import '../../../common/extensions';
import { convertFileToPackage, extractBetweenDelimiters } from '../../common/testUtils';
import { ITestsHelper, ITestsParser, ParserOptions, TestFile, TestFunction, Tests, TestSuite } from '../../common/types';

const DELIMITER = '\'';

@injectable()
export class TestsParser implements ITestsParser {

Expand Down Expand Up @@ -38,15 +37,15 @@ export class TestsParser implements ITestsParser {

const trimmedLine: string = line.trim();

if (trimmedLine.startsWith('<Package \'')) {
if (trimmedLine.startsWith('<Package ')) {
// Process the previous lines.
this.parsePyTestModuleCollectionResult(options.cwd, logOutputLines, testFiles, parentNodes, packagePrefix);
logOutputLines = [''];

packagePrefix = this.extractPackageName(trimmedLine, options.cwd);
}

if (trimmedLine.startsWith('<Module \'') || index === lines.length - 1) {
if (trimmedLine.startsWith('<Module ') || index === lines.length - 1) {
// Process the previous lines.
this.parsePyTestModuleCollectionResult(options.cwd, logOutputLines, testFiles, parentNodes, packagePrefix);
logOutputLines = [''];
Expand Down Expand Up @@ -120,7 +119,7 @@ export class TestsParser implements ITestsParser {
* @param rootDir Value is pytest's `--rootdir=` parameter.
*/
private extractPackageName(packageLine: string, rootDir: string): string {
const packagePath: string = extractBetweenDelimiters(packageLine, DELIMITER, DELIMITER);
const packagePath: string = extractBetweenDelimiters(packageLine, '<Package ', '>').trimQuotes();
let packageName: string = path.normalize(packagePath);
const tmpRoot: string = path.normalize(rootDir);

Expand Down Expand Up @@ -149,10 +148,11 @@ export class TestsParser implements ITestsParser {

lines.forEach(line => {
const trimmedLine = line.trim();
let name: string = extractBetweenDelimiters(trimmedLine, DELIMITER, DELIMITER);
let name: string = '';
const indent = line.indexOf('<');

if (trimmedLine.startsWith('<Module \'')) {
if (trimmedLine.startsWith('<Module ')) {
name = extractBetweenDelimiters(trimmedLine, '<Module ', '>').trimQuotes();
if (packagePrefix && packagePrefix.length > 0) {
name = packagePrefix.concat('/', name);
}
Expand All @@ -169,24 +169,39 @@ export class TestsParser implements ITestsParser {

const parentNode = this.findParentOfCurrentItem(indent, parentNodes);

if (parentNode && trimmedLine.startsWith('<Class \'') || trimmedLine.startsWith('<UnitTestCase \'')) {
const isUnitTest = trimmedLine.startsWith('<UnitTestCase \'');
if (parentNode && trimmedLine.startsWith('<Class ') || trimmedLine.startsWith('<UnitTestCase ')) {
const isUnitTest = trimmedLine.startsWith('<UnitTestCase ');
if (isUnitTest) {
name = extractBetweenDelimiters(trimmedLine, '<UnitTestCase ', '>');
} else {
name = extractBetweenDelimiters(trimmedLine, '<Class ', '>');
}
name = name.trimQuotes();

const rawName = `${parentNode!.item.nameToRun}::${name}`;
const xmlName = `${parentNode!.item.xmlName}.${name}`;
const testSuite: TestSuite = { name: name, nameToRun: rawName, functions: [], suites: [], isUnitTest: isUnitTest, isInstance: false, xmlName: xmlName, time: 0 };
parentNode!.item.suites.push(testSuite);
parentNodes.push({ indent: indent, item: testSuite });
return;
}
if (parentNode && trimmedLine.startsWith('<Instance \'')) {
if (parentNode && trimmedLine.startsWith('<Instance ')) {
name = extractBetweenDelimiters(trimmedLine, '<Instance ', '>').trimQuotes();
// tslint:disable-next-line:prefer-type-cast
const suite = (parentNode!.item as TestSuite);
// suite.rawName = suite.rawName + '::()';
// suite.xmlName = suite.xmlName + '.()';
suite.isInstance = true;
return;
}
if (parentNode && trimmedLine.startsWith('<TestCaseFunction \'') || trimmedLine.startsWith('<Function \'')) {
if (parentNode && trimmedLine.startsWith('<TestCaseFunction ') || trimmedLine.startsWith('<Function ')) {
if (trimmedLine.startsWith('<Function ')) {
name = extractBetweenDelimiters(trimmedLine, '<Function ', '>');
} else {
name = extractBetweenDelimiters(trimmedLine, '<TestCaseFunction ', '>');
}
name = name.trimQuotes();

const rawName = `${parentNode!.item.nameToRun}::${name}`;
const fn: TestFunction = { name: name, nameToRun: rawName, time: 0 };
parentNode!.item.functions.push(fn);
Expand All @@ -209,37 +224,37 @@ export class TestsParser implements ITestsParser {
}
}

/* Sample output from pytest --collect-only
<Module 'test_another.py'>
<Class 'Test_CheckMyApp'>
<Instance '()'>
<Function 'test_simple_check'>
<Function 'test_complex_check'>
<Module 'test_one.py'>
<UnitTestCase 'Test_test1'>
<TestCaseFunction 'test_A'>
<TestCaseFunction 'test_B'>
<Module 'test_two.py'>
<UnitTestCase 'Test_test1'>
<TestCaseFunction 'test_A2'>
<TestCaseFunction 'test_B2'>
<Module 'testPasswords/test_Pwd.py'>
<UnitTestCase 'Test_Pwd'>
<TestCaseFunction 'test_APwd'>
<TestCaseFunction 'test_BPwd'>
<Module 'testPasswords/test_multi.py'>
<Class 'Test_CheckMyApp'>
/* Sample output from pytest --collect-only
<Module 'test_another.py'>
<Class 'Test_CheckMyApp'>
<Instance '()'>
<Function 'test_simple_check'>
<Function 'test_complex_check'>
<Module 'test_one.py'>
<UnitTestCase 'Test_test1'>
<TestCaseFunction 'test_A'>
<TestCaseFunction 'test_B'>
<Module 'test_two.py'>
<UnitTestCase 'Test_test1'>
<TestCaseFunction 'test_A2'>
<TestCaseFunction 'test_B2'>
<Module 'testPasswords/test_Pwd.py'>
<UnitTestCase 'Test_Pwd'>
<TestCaseFunction 'test_APwd'>
<TestCaseFunction 'test_BPwd'>
<Module 'testPasswords/test_multi.py'>
<Class 'Test_CheckMyApp'>
<Instance '()'>
<Function 'test_simple_check'>
<Function 'test_complex_check'>
<Class 'Test_NestedClassA'>
<Instance '()'>
<Function 'test_simple_check'>
<Function 'test_complex_check'>
<Class 'Test_NestedClassA'>
<Function 'test_nested_class_methodB'>
<Class 'Test_nested_classB_Of_A'>
<Instance '()'>
<Function 'test_nested_class_methodB'>
<Class 'Test_nested_classB_Of_A'>
<Instance '()'>
<Function 'test_d'>
<Function 'test_username'>
<Function 'test_parametrized_username[one]'>
<Function 'test_parametrized_username[two]'>
<Function 'test_parametrized_username[three]'>
*/
<Function 'test_d'>
<Function 'test_username'>
<Function 'test_parametrized_username[one]'>
<Function 'test_parametrized_username[two]'>
<Function 'test_parametrized_username[three]'>
*/
Loading