Skip to content
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

Nest subtests under their function in the test explorer. #4668

Merged
1 change: 1 addition & 0 deletions news/1 Enhancements/4503.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show sub-tests in a subtree in the test explorer.
84 changes: 56 additions & 28 deletions src/client/unittests/common/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,14 @@ export function getParent(tests: Tests, data: TestDataItem): TestDataItem | unde
}
case TestType.testSuite: {
const suite = data as TestSuite;
// const parentSuite = tests.testSuites.find(item => item.testSuite.suites.some(child => child === data));
// const parentFile = tests.testFiles.find(item=> item.suites.find(data)
// return item && (item.parentTestSuite || item.parentTestFile);
if (isSubtestsParent(suite)) {
const fn = suite.functions[0];
const parent = tests.testSuites.find(item => item.testSuite.functions.indexOf(fn) >= 0);
if (parent) {
return parent.testSuite;
}
return tests.testFiles.find(item => item.functions.indexOf(fn) >= 0);
}
const parentSuite = tests.testSuites.find(item => item.testSuite.suites.indexOf(suite) >= 0);
if (parentSuite) {
return parentSuite.testSuite;
Expand All @@ -325,13 +330,14 @@ export function getParent(tests: Tests, data: TestDataItem): TestDataItem | unde
}
case TestType.testFunction: {
const fn = data as TestFunction;
if (fn.subtestParent) {
return fn.subtestParent.asSuite;
}
const parentSuite = tests.testSuites.find(item => item.testSuite.functions.indexOf(fn) >= 0);
if (parentSuite) {
return parentSuite.testSuite;
}
return tests.testFiles.find(item => item.functions.indexOf(fn) >= 0);
// const item = findFlattendTestFunction(tests, data as TestFunction);
// return item && (item.parentTestSuite || item.parentTestFile);
}
default: {
throw new Error('Unknown test type');
Expand Down Expand Up @@ -377,20 +383,6 @@ function getParentTestFolderForFolder(tests: Tests, folder: TestFolder): TestFol
return;
}
return tests.testFolders.find(item => item.folders.some(child => child === folder));
// function getParentFolder(folders: TestFolder[], item: TestFolder): TestFolder {
// const index = folders.indexOf(item);
// if (index) {
// return folders[index];
// }
// for (const f of folders) {
// const found = getParentFolder(f.folders, item);
// if (found) {
// return found;
// }
// }
// }

// return getParentFolder(tests.testFolders, folder);
}

/**
Expand Down Expand Up @@ -427,22 +419,30 @@ export function findFlattendTestSuite(tests: Tests, suite: TestSuite): Flattened
*/
export function getChildren(item: TestDataItem): TestDataItem[] {
switch (getTestType(item)) {
case TestType.testFile: {
return [
...(item as TestFile).functions,
...(item as TestFile).suites
];
}
case TestType.testFolder: {
return [
...(item as TestFolder).folders,
...(item as TestFolder).testFiles
];
}
case TestType.testFile: {
const [subSuites, functions] = divideSubtests((item as TestFile).functions);
return [
...functions,
...(item as TestFile).suites,
...subSuites
];
}
case TestType.testSuite: {
let subSuites: TestSuite[] = [];
let functions = (item as TestSuite).functions;
if (!isSubtestsParent((item as TestSuite))) {
[subSuites, functions] = divideSubtests((item as TestSuite).functions);
}
return [
...(item as TestSuite).functions,
...(item as TestSuite).suites
...functions,
...(item as TestSuite).suites,
...subSuites
];
}
case TestType.testFunction: {
Expand All @@ -454,6 +454,34 @@ export function getChildren(item: TestDataItem): TestDataItem[] {
}
}

function divideSubtests(mixed: TestFunction[]): [TestSuite[], TestFunction[]] {
const suites: TestSuite[] = [];
const functions: TestFunction[] = [];
mixed.forEach(func => {
if (!func.subtestParent) {
functions.push(func);
return;
}
const parent = func.subtestParent.asSuite;
if (suites.indexOf(parent) < 0) {
suites.push(parent);
}
});
return [suites, functions];
}

export function isSubtestsParent(suite: TestSuite): boolean {
const functions = suite.functions;
if (functions.length === 0) {
return false;
}
const subtestParent = functions[0].subtestParent;
if (subtestParent === undefined) {
return false;
}
return subtestParent.asSuite === suite;
}

export function copyTestResults(source: Tests, target: Tests): void {
copyResultsForFolders(source.testFolders, target.testFolders);
}
Expand Down Expand Up @@ -511,4 +539,4 @@ function copyValueTypes<T>(source: T, target: T): void {
target[key] = value;
}
});
}
}
7 changes: 7 additions & 0 deletions src/client/unittests/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export type TestFunction = TestResult & {
resource: Uri;
name: string;
nameToRun: string;
subtestParent?: SubtestParent;
};

export type SubtestParent = TestResult & {
name: string;
nameToRun: string;
asSuite: TestSuite;
};

export type TestResult = Node & {
Expand Down
8 changes: 6 additions & 2 deletions src/client/unittests/explorer/testTreeViewItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { Commands } from '../../common/constants';
import { getIcon } from '../../common/utils/icons';
import { noop } from '../../common/utils/misc';
import { Icons } from '../common/constants';
import { getTestType } from '../common/testUtils';
import { TestResult, TestStatus, TestType } from '../common/types';
import { getTestType, isSubtestsParent } from '../common/testUtils';
import { TestResult, TestStatus, TestSuite, TestType } from '../common/types';
import { TestDataItem } from '../types';

/**
Expand Down Expand Up @@ -114,6 +114,10 @@ export class TestTreeItem extends TreeItem {
break;
}
case TestType.testSuite: {
if (isSubtestsParent(this.data as TestSuite)) {
this.command = { command: Commands.navigateToTestFunction, title: 'Open', arguments: [this.resource, this.data, false] };
break;
}
this.command = { command: Commands.navigateToTestSuite, title: 'Open', arguments: [this.resource, this.data, false] };
break;
}
Expand Down
81 changes: 73 additions & 8 deletions src/client/unittests/pytest/services/parserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import * as path from 'path';
import { Uri } from 'vscode';
import '../../../common/extensions';
import { convertFileToPackage, extractBetweenDelimiters } from '../../common/testUtils';
import { ITestsHelper, ITestsParser, ParserOptions, TestFile, TestFunction, Tests, TestSuite } from '../../common/types';
import {
ITestsHelper, ITestsParser, ParserOptions, SubtestParent,
TestFile, TestFunction, Tests, TestSuite
} from '../../common/types';

@injectable()
export class TestsParser implements ITestsParser {
Expand Down Expand Up @@ -138,6 +141,7 @@ export class TestsParser implements ITestsParser {
return packageName;
}

// tslint:disable-next-line:max-func-body-length
private parsePyTestModuleCollectionResult(
rootDirectory: string,
lines: string[],
Expand All @@ -148,6 +152,8 @@ export class TestsParser implements ITestsParser {

let currentPackage: string = '';
const resource = Uri.file(rootDirectory);

// tslint:disable-next-line:cyclomatic-complexity max-func-body-length
lines.forEach(line => {
const trimmedLine = line.trim();
let name: string = '';
Expand All @@ -161,9 +167,17 @@ export class TestsParser implements ITestsParser {
currentPackage = convertFileToPackage(name);
const fullyQualifiedName = path.isAbsolute(name) ? name : path.resolve(rootDirectory, name);
const testFile = {
resource,
functions: [], suites: [], name: name, fullPath: fullyQualifiedName,
nameToRun: name, xmlName: currentPackage, time: 0, functionsPassed: 0, functionsFailed: 0, functionsDidNotRun: 0
resource: resource,
functions: [],
suites: [],
name: name,
fullPath: fullyQualifiedName,
nameToRun: name,
xmlName: currentPackage,
time: 0,
functionsPassed: 0,
functionsFailed: 0,
functionsDidNotRun: 0
};
testFiles.push(testFile);
parentNodes.push({ indent: indent, item: testFile });
Expand All @@ -183,7 +197,20 @@ export class TestsParser implements ITestsParser {

const rawName = `${parentNode!.item.nameToRun}::${name}`;
const xmlName = `${parentNode!.item.xmlName}.${name}`;
const testSuite: TestSuite = { resource, name: name, nameToRun: rawName, functions: [], suites: [], isUnitTest: isUnitTest, isInstance: false, xmlName: xmlName, time: 0, functionsPassed: 0, functionsFailed: 0, functionsDidNotRun: 0 };
const testSuite: TestSuite = {
resource: resource,
name: name,
nameToRun: rawName,
functions: [],
suites: [],
isUnitTest: isUnitTest,
isInstance: false,
xmlName: xmlName,
time: 0,
functionsPassed: 0,
functionsFailed: 0,
functionsDidNotRun: 0
};
parentNode!.item.suites.push(testSuite);
parentNodes.push({ indent: indent, item: testSuite });
return;
Expand All @@ -192,8 +219,6 @@ export class TestsParser implements ITestsParser {
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;
}
Expand All @@ -206,7 +231,47 @@ export class TestsParser implements ITestsParser {
name = name.trimQuotes();

const rawName = `${parentNode!.item.nameToRun}::${name}`;
const fn: TestFunction = { resource, name: name, nameToRun: rawName, time: 0 };
const fn: TestFunction = {
resource: resource,
name: name,
nameToRun: rawName,
time: 0
};
const pos = name.indexOf('[');
if (pos > 0 && name.endsWith(']')) {
const funcName = name.substring(0, pos);
const subtest = name.substring(pos);

let subtestParent: SubtestParent | undefined;
const last = parentNode!.item.functions.pop();
if (last) {
parentNode!.item.functions.push(last);
if (last.subtestParent && last.subtestParent.name === funcName) {
subtestParent = last.subtestParent;
}
}
if (!subtestParent) {
const subtestsSuite: TestSuite = {
resource: resource,
name: funcName,
nameToRun: rawName.substring(0, rawName.length - subtest.length),
functions: [],
suites: [],
isUnitTest: false,
isInstance: false,
xmlName: '',
time: 0
};
subtestParent = {
name: subtestsSuite.name,
nameToRun: subtestsSuite.nameToRun,
asSuite: subtestsSuite,
time: 0
};
}
fn.subtestParent = subtestParent!;
subtestParent.asSuite.functions.push(fn);
}
parentNode!.item.functions.push(fn);
return;
}
Expand Down
Loading