From 4a5e485c9bc45471eff7d13665d0d960fbba6026 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 29 Aug 2019 15:03:47 -0600 Subject: [PATCH 01/30] Clean up the file a little. --- src/client/testing/common/xUnitParser.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index e315ae9b8341..a062af8f7873 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import { injectable } from 'inversify'; import { IXUnitParser, PassCalculationFormulae, Tests, TestStatus } from './types'; + type TestSuiteResult = { $: { errors: string; @@ -44,11 +45,24 @@ function getSafeInt(value: string, defaultValue: any = 0): number { @injectable() export class XUnitParser implements IXUnitParser { - public updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise { - return updateResultsFromXmlLogFile(tests, outputXmlFile, passCalculationFormulae); + public updateResultsFromXmlLogFile( + tests: Tests, + outputXmlFile: string, + passCalculationFormulae: PassCalculationFormulae + ): Promise { + return updateResultsFromXmlLogFile( + tests, + outputXmlFile, + passCalculationFormulae + ); } } -export function updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise { + +function updateResultsFromXmlLogFile( + tests: Tests, + outputXmlFile: string, + passCalculationFormulae: PassCalculationFormulae +): Promise { // tslint:disable-next-line:no-any return new Promise((resolve, reject) => { fs.readFile(outputXmlFile, 'utf8', (err, data) => { From b2883f5722cce6aece0cd93130432f76b739287c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 29 Aug 2019 15:51:24 -0600 Subject: [PATCH 02/30] Use IFileSystem instead of fs directly. --- src/client/testing/common/xUnitParser.ts | 184 +++++++++--------- .../pytest/pytest.testMessageService.test.ts | 6 +- 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index a062af8f7873..0a6545324ad1 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs'; -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; +import { IFileSystem } from '../../common/platform/types'; import { IXUnitParser, PassCalculationFormulae, Tests, TestStatus } from './types'; type TestSuiteResult = { @@ -45,12 +45,17 @@ function getSafeInt(value: string, defaultValue: any = 0): number { @injectable() export class XUnitParser implements IXUnitParser { + constructor( + @inject(IFileSystem) private readonly fs: IFileSystem + ) { } + public updateResultsFromXmlLogFile( tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae ): Promise { return updateResultsFromXmlLogFile( + this.fs, tests, outputXmlFile, passCalculationFormulae @@ -58,104 +63,95 @@ export class XUnitParser implements IXUnitParser { } } -function updateResultsFromXmlLogFile( +async function updateResultsFromXmlLogFile( + fs: IFileSystem, tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae -): Promise { - // tslint:disable-next-line:no-any - return new Promise((resolve, reject) => { - fs.readFile(outputXmlFile, 'utf8', (err, data) => { - if (err) { - return reject(err); +) { + const data = await fs.readFile(outputXmlFile); + + // tslint:disable-next-line:no-require-imports + const xml2js = require('xml2js'); + xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { + if (error) { + throw error; + //return reject(error); + } + + const testSuiteResult: TestSuiteResult = parserResult.testsuite; + tests.summary.errors = getSafeInt(testSuiteResult.$.errors); + tests.summary.failures = getSafeInt(testSuiteResult.$.failures); + tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); + const testCount = getSafeInt(testSuiteResult.$.tests); + + switch (passCalculationFormulae) { + case PassCalculationFormulae.pytest: { + tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; + break; } - // tslint:disable-next-line:no-require-imports - const xml2js = require('xml2js'); - xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { - if (error) { - return reject(error); - } - try { - const testSuiteResult: TestSuiteResult = parserResult.testsuite; - tests.summary.errors = getSafeInt(testSuiteResult.$.errors); - tests.summary.failures = getSafeInt(testSuiteResult.$.failures); - tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); - const testCount = getSafeInt(testSuiteResult.$.tests); - - switch (passCalculationFormulae) { - case PassCalculationFormulae.pytest: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - case PassCalculationFormulae.nosetests: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - default: { - throw new Error('Unknown Test Pass Calculation'); - } - } - - if (!Array.isArray(testSuiteResult.testcase)) { - return resolve(); - } - - testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { - const xmlClassName = testcase.$.classname.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); - const result = tests.testFunctions.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === testcase.$.name); - if (!result) { - // Possible we're dealing with nosetests, where the file name isn't returned to us - // When dealing with nose tests - // It is possible to have a test file named x in two separate test sub directories and have same functions/classes - // And unforutnately xunit log doesn't ouput the filename - - // result = tests.testFunctions.find(fn => fn.testFunction.name === testcase.$.name && - // fn.parentTestSuite && fn.parentTestSuite.name === testcase.$.classname); - - // Look for failed file test - const fileTest = testcase.$.file && tests.testFiles.find(file => file.nameToRun === testcase.$.file); - if (fileTest && testcase.error) { - fileTest.status = TestStatus.Error; - fileTest.passed = false; - fileTest.message = testcase.error[0].$.message; - fileTest.traceback = testcase.error[0]._; - } - return; - } - - result.testFunction.line = getSafeInt(testcase.$.line, null); - result.testFunction.file = testcase.$.file; - result.testFunction.time = parseFloat(testcase.$.time); - result.testFunction.passed = true; - result.testFunction.status = TestStatus.Pass; - - if (testcase.failure) { - result.testFunction.status = TestStatus.Fail; - result.testFunction.passed = false; - result.testFunction.message = testcase.failure[0].$.message; - result.testFunction.traceback = testcase.failure[0]._; - } - - if (testcase.error) { - result.testFunction.status = TestStatus.Error; - result.testFunction.passed = false; - result.testFunction.message = testcase.error[0].$.message; - result.testFunction.traceback = testcase.error[0]._; - } - - if (testcase.skipped) { - result.testFunction.status = TestStatus.Skipped; - result.testFunction.passed = undefined; - result.testFunction.message = testcase.skipped[0].$.message; - result.testFunction.traceback = ''; - } - }); - } catch (ex) { - return reject(ex); + case PassCalculationFormulae.nosetests: { + tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; + break; + } + default: { + throw new Error('Unknown Test Pass Calculation'); + } + } + + if (!Array.isArray(testSuiteResult.testcase)) { + return; + } + + testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { + const xmlClassName = testcase.$.classname.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); + const result = tests.testFunctions.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === testcase.$.name); + if (!result) { + // Possible we're dealing with nosetests, where the file name isn't returned to us + // When dealing with nose tests + // It is possible to have a test file named x in two separate test sub directories and have same functions/classes + // And unforutnately xunit log doesn't ouput the filename + + // result = tests.testFunctions.find(fn => fn.testFunction.name === testcase.$.name && + // fn.parentTestSuite && fn.parentTestSuite.name === testcase.$.classname); + + // Look for failed file test + const fileTest = testcase.$.file && tests.testFiles.find(file => file.nameToRun === testcase.$.file); + if (fileTest && testcase.error) { + fileTest.status = TestStatus.Error; + fileTest.passed = false; + fileTest.message = testcase.error[0].$.message; + fileTest.traceback = testcase.error[0]._; } + return; + } - resolve(); - }); + result.testFunction.line = getSafeInt(testcase.$.line, null); + result.testFunction.file = testcase.$.file; + result.testFunction.time = parseFloat(testcase.$.time); + result.testFunction.passed = true; + result.testFunction.status = TestStatus.Pass; + + if (testcase.failure) { + result.testFunction.status = TestStatus.Fail; + result.testFunction.passed = false; + result.testFunction.message = testcase.failure[0].$.message; + result.testFunction.traceback = testcase.failure[0]._; + } + + if (testcase.error) { + result.testFunction.status = TestStatus.Error; + result.testFunction.passed = false; + result.testFunction.message = testcase.error[0].$.message; + result.testFunction.traceback = testcase.error[0]._; + } + + if (testcase.skipped) { + result.testFunction.status = TestStatus.Skipped; + result.testFunction.passed = undefined; + result.testFunction.message = testcase.skipped[0].$.message; + result.testFunction.traceback = ''; + } }); }); } diff --git a/src/test/testing/pytest/pytest.testMessageService.test.ts b/src/test/testing/pytest/pytest.testMessageService.test.ts index 69be5d7d624d..2e573cedd97c 100644 --- a/src/test/testing/pytest/pytest.testMessageService.test.ts +++ b/src/test/testing/pytest/pytest.testMessageService.test.ts @@ -12,6 +12,8 @@ import * as vscode from 'vscode'; import { IWorkspaceService } from '../../../client/common/application/types'; import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; import { ProductNames } from '../../../client/common/installer/productNames'; +import { FileSystem } from '../../../client/common/platform/fileSystem'; +import { PlatformService } from '../../../client/common/platform/platformService'; import { Product } from '../../../client/common/types'; import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; import { InterpreterService } from '../../../client/interpreter/interpreterService'; @@ -99,6 +101,8 @@ async function getExpectedLocationStackFromTestDetails(testDetails: ITestDetails suite('Unit Tests - PyTest - TestMessageService', () => { let ioc: UnitTestIocContainer; + const platformService = new PlatformService(); + const filesystem = new FileSystem(platformService); const configTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; suiteSetup(async () => { await initialize(); @@ -141,7 +145,7 @@ suite('Unit Tests - PyTest - TestMessageService', () => { const discoveredTest: DiscoveredTests[] = JSON.parse(discoveryOutput); options.workspaceFolder = vscode.Uri.file(discoveredTest[0].root); const parsedTests: Tests = parser.parse(options.workspaceFolder, discoveredTest); - const xUnitParser = new XUnitParser(); + const xUnitParser = new XUnitParser(filesystem); await xUnitParser.updateResultsFromXmlLogFile(parsedTests, path.join(PYTEST_RESULTS_PATH, scenario.runOutput), PassCalculationFormulae.pytest); const testResultsService = new TestResultsService(testVisitor.object); testResultsService.updateResults(parsedTests); From 2cd1507ec662a2dcd2985963f617f4c7c7a9161a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 29 Aug 2019 15:51:56 -0600 Subject: [PATCH 03/30] Add missing doc comments. --- src/client/testing/common/types.ts | 1 + src/client/testing/common/xUnitParser.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 4723c60ff4ce..21f7d49ddafc 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -290,6 +290,7 @@ export enum PassCalculationFormulae { export const IXUnitParser = Symbol('IXUnitParser'); export interface IXUnitParser { + // Update "tests" with the results parsed from the given file. updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise; } diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 0a6545324ad1..fec34be61860 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -63,6 +63,7 @@ export class XUnitParser implements IXUnitParser { } } +// Update "tests" with the results parsed from the given file. async function updateResultsFromXmlLogFile( fs: IFileSystem, tests: Tests, From a69667b8280a7def5417dcd78e74202647cab978 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 5 Sep 2019 17:02:50 -0600 Subject: [PATCH 04/30] Add a commented-out testing helper. --- src/client/testing/common/xUnitParser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index fec34be61860..95e0daefbfe5 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -71,6 +71,8 @@ async function updateResultsFromXmlLogFile( passCalculationFormulae: PassCalculationFormulae ) { const data = await fs.readFile(outputXmlFile); + // Un-comment this line to capture the results file for later use in tests: + //await fs.writeFile('/tmp/results.xml', data); // tslint:disable-next-line:no-require-imports const xml2js = require('xml2js'); From 46fa9c475f82dfd47ae93a8e94ae8ff83975f4b2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 5 Sep 2019 17:03:20 -0600 Subject: [PATCH 05/30] Add some general testing helpers. --- src/test/testing/helper.ts | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/test/testing/helper.ts b/src/test/testing/helper.ts index 846fa70dc386..9f4a75c405cb 100644 --- a/src/test/testing/helper.ts +++ b/src/test/testing/helper.ts @@ -3,9 +3,12 @@ import * as assert from 'assert'; import { sep } from 'path'; +import { Uri } from 'vscode'; import { IS_WINDOWS } from '../../client/common/platform/constants'; import { Tests } from '../../client/testing/common/types'; +export const RESOURCE = Uri.file(__filename); + export function lookForTestFile(tests: Tests, testFile: string) { let found: boolean; // Perform case insensitive search on windows. @@ -19,3 +22,53 @@ export function lookForTestFile(tests: Tests, testFile: string) { } assert.equal(found, true, `Test File not found '${testFile}'`); } + +// Return a filename that uses the OS-specific path separator. +// +// Only "/" (forward slash) in the given filename is affected. +// +// This helps with readability in test code. It allows us to use +// literals for filenames and dirnames instead of oath.join(). +export function fixPath(filename: string): string { + return filename.replace(/\//, sep); +} + +// Return the indentation part of the given line. +export function getIndent(line: string): string { + const found = line.match(/^ */); + return found![0]; +} + +// Return the dedented lines in the given text. +// +// This is used to represent text concisely and readably, which is +// particularly useful for declarative definitions (e.g. in tests). +// +// (inspired by Python's textwrap.dedent()) +export function getDedentedLines(text: string): string[] { + const linesep = text.includes('\r') ? '\r\n' : '\n'; + const lines = text.split(linesep); + if (!lines) { + return [text]; + } + + if (lines[0] !== '') { + throw Error('expected actual first line to be blank'); + } + lines.shift(); + + if (lines[0] === '') { + throw Error('expected "first" line to not be blank'); + } + const leading = getIndent(lines[0]).length; + + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (getIndent(line).length < leading) { + throw Error(`line ${i} has less indent than the "first" line`); + } + lines[i] = line.substring(leading); + } + + return lines; +} From 64f2760c0c3bfea33f6bd5300960561c54d2641f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 5 Sep 2019 17:04:51 -0600 Subject: [PATCH 06/30] Add testing helpers for test nodes. --- src/test/testing/helpers-declarative.ts | 306 ++++++++++++++++++++++++ src/test/testing/helpers-nodes.ts | 233 ++++++++++++++++++ src/test/testing/helpers-results.ts | 114 +++++++++ 3 files changed, 653 insertions(+) create mode 100644 src/test/testing/helpers-declarative.ts create mode 100644 src/test/testing/helpers-nodes.ts create mode 100644 src/test/testing/helpers-results.ts diff --git a/src/test/testing/helpers-declarative.ts b/src/test/testing/helpers-declarative.ts new file mode 100644 index 000000000000..a24caf88c446 --- /dev/null +++ b/src/test/testing/helpers-declarative.ts @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Uri } from 'vscode'; +import { + TestFile, TestFolder, TestFunction, TestProvider, TestResult, Tests, + TestStatus, TestSuite, TestType +} from '../../client/testing/common/types'; +import { + getDedentedLines, getIndent, RESOURCE +} from './helper'; +import { + addDiscoveredFile, addDiscoveredSubFolder, addDiscoveredSuite, + addDiscoveredTest, createFolderResults, TestNode +} from './helpers-nodes'; +import { + createEmptyResults, flattenFunction, flattenSuite, updateSummary +} from './helpers-results'; + +type ParsedTestNode = { + indent: string; + name: string; + testType: TestType; + result: TestResult; +}; + +type TestParent = TestNode & { + indent: string; +}; + +// Return a test tree built from concise declarative text. +export function createResults( + text: string, + provider: TestProvider = 'pytest', + resource: Uri = RESOURCE +): Tests { + const tests = createEmptyResults(); + + // Build the tree (and populate the return value at the same time). + const parents: TestParent[] = []; + let prev: TestParent; + for (const line of getDedentedLines(text)) { + if (line.trim() === '') { + continue; + } + const parsed = parseTestLine(line); + + let node: TestNode; + if (isRootNode(parsed)) { + parents.length = 0; // Clear the array. + node = createFolderResults( + parsed.name, + undefined, + resource + ); + tests.rootTestFolders.push(node as TestFolder); + tests.testFolders.push(node as TestFolder); + } else { + const parent = setMatchingParent( + parents, + prev!, + parsed.indent + ); + node = buildDiscoveredChildNode( + parent, + parsed.name, + parsed.testType, + provider, + resource + ); + switch (parsed.testType) { + case TestType.testFolder: + tests.testFolders.push(node as TestFolder); + break; + case TestType.testFile: + tests.testFiles.push(node as TestFile); + break; + case TestType.testSuite: + tests.testSuites.push( + flattenSuite(node as TestSuite, parents) + ); + break; + case TestType.testFunction: + // This does not deal with subtests? + tests.testFunctions.push( + flattenFunction(node as TestFunction, parents) + ); + break; + default: + } + } + + // Set the result. + node.status = parsed.result.status; + node.time = parsed.result.time; + updateSummary(tests.summary, node.status!); + + // Prepare for the next line. + prev = node as TestParent; + prev.indent = parsed.indent; + } + + return tests; +} + +// Determine the kind, indent, and result info based on the line. +function parseTestLine(line: string): ParsedTestNode { + if (line.includes('\\')) { + throw Error('expected / as path separator (even on Windows)'); + } + + const indent = getIndent(line); + line = line.trim(); + + const parts = line.split(' '); + let name = parts.shift(); + if (!name) { + throw Error('missing name'); + } + + // Determine the type from the name. + let testType: TestType; + if (name.endsWith('/')) { + // folder + testType = TestType.testFolder; + while (name.endsWith('/')) { + name = name.slice(0, -1); + } + } else if (name.includes('.')) { + // file + if (name.includes('/')) { + throw Error('filename must not include directories'); + } + testType = TestType.testFile; + } else if (name.startsWith('<')) { + // suite + if (!name.endsWith('>')) { + throw Error('suite missing closing bracket'); + } + testType = TestType.testSuite; + name = name.slice(1, -1); + } else { + // test + testType = TestType.testFunction; + } + + // Parse the results. + const result: TestResult = { + time: 0 + }; + if (parts.length !== 0 && testType !== TestType.testFunction) { + throw Error('non-test nodes do not have results'); + } + switch (parts.length) { + case 0: + break; + case 1: + // tslint:disable-next-line:no-any + if (isNaN(parts[0] as any)) { + throw Error(`expected a time (float), got ${parts[0]}`); + } + result.time = parseFloat(parts[0]); + break; + case 2: + switch (parts[0]) { + case 'P': + result.status = TestStatus.Pass; + break; + case 'F': + result.status = TestStatus.Fail; + break; + case 'E': + result.status = TestStatus.Error; + break; + case 'S': + result.status = TestStatus.Skipped; + break; + default: + throw Error('expected a status and then a time'); + } + // tslint:disable-next-line:no-any + if (isNaN(parts[1] as any)) { + throw Error(`expected a time (float), got ${parts[1]}`); + } + result.time = parseFloat(parts[1]); + break; + default: + throw Error('too many items on line'); + } + + return { + indent: indent, + name: name, + testType: testType, + result: result + }; +} + +function isRootNode( + parsed: ParsedTestNode +): boolean { + if (parsed.indent === '') { + if (parsed.testType !== TestType.testFolder) { + throw Error('a top-level node must be a folder'); + } + return true; + } + return false; +} + +function setMatchingParent( + parents: TestParent[], + prev: TestParent, + parsedIndent: string +): TestParent { + let current = parents.length > 0 ? parents[parents.length - 1] : prev; + if (parsedIndent.length > current.indent.length) { + parents.push(prev); + current = prev; + } else { + while (parsedIndent !== current.indent) { + if (parsedIndent.length > current.indent.length) { + throw Error('mis-aligned indentation'); + } + + parents.pop(); + if (parents.length === 0) { + throw Error('mis-aligned indentation'); + } + current = parents[parents.length - 1]; + } + } + return current; +} + +function buildDiscoveredChildNode( + parent: TestParent, + name: string, + testType: TestType, + provider: TestProvider, + resource?: Uri +): TestNode { + switch (testType) { + case TestType.testFolder: + if (parent.testType !== TestType.testFolder) { + throw Error('parent must be a folder'); + } + return addDiscoveredSubFolder( + parent as TestFolder, + name, + undefined, + resource + ); + case TestType.testFile: + if (parent.testType !== TestType.testFolder) { + throw Error('parent must be a folder'); + } + return addDiscoveredFile( + parent as TestFolder, + name, + undefined, + undefined, + resource + ); + case TestType.testSuite: + let suiteParent: TestFile | TestSuite; + if (parent.testType === TestType.testFile) { + suiteParent = parent as TestFile; + } else if (parent.testType === TestType.testSuite) { + suiteParent = parent as TestSuite; + } else { + throw Error('parent must be a file or suite'); + } + return addDiscoveredSuite( + suiteParent, + name, + undefined, + undefined, + provider, + undefined, + resource + ); + case TestType.testFunction: + let funcParent: TestFile | TestSuite; + if (parent.testType === TestType.testFile) { + funcParent = parent as TestFile; + } else if (parent.testType === TestType.testSuite) { + funcParent = parent as TestSuite; + } else if (parent.testType === TestType.testFunction) { + throw Error('not finished: use addDiscoveredSubTest()'); + } else { + throw Error('parent must be a file, suite, or function'); + } + return addDiscoveredTest( + funcParent, + name, + undefined, + provider, + resource + ); + default: + throw Error('unsupported'); + } +} diff --git a/src/test/testing/helpers-nodes.ts b/src/test/testing/helpers-nodes.ts new file mode 100644 index 000000000000..81a236530e2c --- /dev/null +++ b/src/test/testing/helpers-nodes.ts @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { + SubtestParent, TestFile, TestFolder, TestFunction, TestProvider, + TestStatus, TestSuite, TestType +} from '../../client/testing/common/types'; +import { fixPath, RESOURCE } from './helper'; + +type SuperTest = TestFunction & { + subtests: TestFunction[]; +}; + +export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; + +export type TestNode = TestItem & { + testType: TestType; +}; + +// Set the result-oriented properties back to their "unset" values. +export function resetResult(node: TestNode) { + node.time = 0; + node.status = TestStatus.Unknown; +} + +//******************************** +// builders for empty low-level test results + +export function createFolderResults( + dirname: string, + nameToRun?: string, + resource: Uri = RESOURCE +): TestNode { + dirname = fixPath(dirname); + return { + resource: resource, + name: dirname, + nameToRun: nameToRun || dirname, + folders: [], + testFiles: [], + testType: TestType.testFolder, + // result + time: 0, + status: TestStatus.Unknown + }; +} + +export function createFileResults( + filename: string, + nameToRun?: string, + xmlName?: string, + resource: Uri = RESOURCE +): TestNode { + filename = fixPath(filename); + if (!xmlName) { + xmlName = filename + .replace(/\.[^.]+$/, '') + .replace(RegExp(path.sep), '.') + .replace(/^[.\\\/]*/, ''); + } + return { + resource: resource, + fullPath: filename, + name: path.basename(filename), + nameToRun: nameToRun || filename, + xmlName: xmlName!, + suites: [], + functions: [], + testType: TestType.testFile, + // result + time: 0, + status: TestStatus.Unknown + }; +} + +export function createSuiteResults( + name: string, + nameToRun?: string, + xmlName?: string, + provider: TestProvider = 'pytest', + isInstance: boolean = false, + resource: Uri = RESOURCE +): TestNode { + return { + resource: resource, + name: name, + nameToRun: nameToRun || '', // must be set for parent + xmlName: xmlName || '', // must be set for parent + isUnitTest: provider === 'unittest', + isInstance: isInstance, + suites: [], + functions: [], + testType: TestType.testSuite, + // result + time: 0, + status: TestStatus.Unknown + }; +} + +export function createTestResults( + name: string, + nameToRun?: string, + subtestParent?: SubtestParent, + resource: Uri = RESOURCE +): TestNode { + return { + resource: resource, + name: name, + nameToRun: nameToRun || name, + subtestParent: subtestParent, + testType: TestType.testFunction, + // result + time: 0, + status: TestStatus.Unknown + }; +} + +//******************************** +// adding children to low-level nodes + +export function addDiscoveredSubFolder( + parent: TestFolder, + basename: string, + nameToRun?: string, + resource?: Uri +): TestNode { + const dirname = path.join(parent.name, fixPath(basename)); + const subFolder = createFolderResults( + dirname, + nameToRun, + resource || parent.resource || RESOURCE + ); + parent.folders.push(subFolder as TestFolder); + return subFolder; +} + +export function addDiscoveredFile( + parent: TestFolder, + basename: string, + nameToRun?: string, + xmlName?: string, + resource?: Uri +): TestNode { + const filename = path.join(parent.name, fixPath(basename)); + const file = createFileResults( + filename, + nameToRun, + xmlName, + resource || parent.resource || RESOURCE + ); + parent.testFiles.push(file as TestFile); + return file; +} + +export function addDiscoveredSuite( + parent: TestFile | TestSuite, + name: string, + nameToRun?: string, + xmlName?: string, + provider: TestProvider = 'pytest', + isInstance?: boolean, + resource?: Uri +): TestNode { + if (!nameToRun) { + const sep = provider === 'pytest' ? '::' : '.'; + nameToRun = `${parent.nameToRun}${sep}${name}`; + } + const suite = createSuiteResults( + name, + nameToRun!, + xmlName || `${parent.xmlName}.${name}`, + provider, + isInstance, + resource || parent.resource || RESOURCE + ); + parent.suites.push(suite as TestSuite); + return suite; +} + +export function addDiscoveredTest( + parent: TestFile | TestSuite, + name: string, + nameToRun?: string, + provider: TestProvider = 'pytest', + resource?: Uri +): TestNode { + if (!nameToRun) { + const sep = provider === 'pytest' ? '::' : '.'; + nameToRun = `${parent.nameToRun}${sep}${name}`; + } + const test = createTestResults( + name, + nameToRun, + undefined, + resource || parent.resource || RESOURCE + ); + parent.functions.push(test as TestFunction); + return test; +} + +export function addDiscoveredSubtest( + parent: SuperTest, + name: string, + nameToRun?: string, + provider: TestProvider = 'pytest', + resource?: Uri +): TestNode { + const subtest = createTestResults( + name, + nameToRun!, + { + name: parent.name, + nameToRun: parent.nameToRun, + asSuite: createSuiteResults( + parent.name, + parent.nameToRun, + '', + provider, + false, + parent.resource + ) as TestSuite, + time: 0 + }, + resource || parent.resource || RESOURCE + ); + (subtest as TestFunction).subtestParent!.asSuite.functions.push(subtest); + parent.subtests.push(subtest as TestFunction); + return subtest; +} diff --git a/src/test/testing/helpers-results.ts b/src/test/testing/helpers-results.ts new file mode 100644 index 000000000000..43ac2dd0a69f --- /dev/null +++ b/src/test/testing/helpers-results.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { + FlattenedTestFunction, FlattenedTestSuite, TestFile, TestFunction, + Tests, TestStatus, TestSuite, TestSummary, TestType +} from '../../client/testing/common/types'; +import { + TestNode +} from './helpers-nodes'; + +// Return an initialized test results. +export function createEmptyResults(): Tests { + return { + summary: { + passed: 0, + failures: 0, + errors: 0, + skipped: 0 + }, + testFiles: [], + testFunctions: [], + testSuites: [], + testFolders: [], + rootTestFolders: [] + }; +} + +// Increment the appropriate summary property. +export function updateSummary( + summary: TestSummary, + status: TestStatus +) { + switch (status) { + case TestStatus.Pass: + summary.passed += 1; + break; + case TestStatus.Fail: + summary.failures += 1; + break; + case TestStatus.Error: + summary.errors += 1; + break; + case TestStatus.Skipped: + summary.skipped += 1; + break; + default: + // Do not update the results. + } +} + +// Return the file found walking up the parents, if any. +// +// There should only be one parent file. +export function findParentFile(parents: TestNode[]): TestFile | undefined { + // Iterate in reverse order. + for (let i = parents.length; i > 0; i -= 1) { + const parent = parents[i - 1]; + if (parent.testType === TestType.testFile) { + return parent as TestFile; + } + } + return; +} + +// Return the first suite found walking up the parents, if any. +export function findParentSuite(parents: TestNode[]): TestSuite | undefined { + // Iterate in reverse order. + for (let i = parents.length; i > 0; i -= 1) { + const parent = parents[i - 1]; + if (parent.testType === TestType.testSuite) { + return parent as TestSuite; + } + } + return; +} + +// Return the "flattened" test suite node. +export function flattenSuite( + node: TestSuite, + parents: TestNode[] +): FlattenedTestSuite { + const found = findParentFile(parents); + if (!found) { + throw Error('parent file not found'); + } + const parentFile: TestFile = found; + return { + testSuite: node, + parentTestFile: parentFile, + xmlClassName: node.xmlName + }; +} + +// Return the "flattened" test function node. +export function flattenFunction( + node: TestFunction, + parents: TestNode[] +): FlattenedTestFunction { + const found = findParentFile(parents); + if (!found) { + throw Error('parent file not found'); + } + const parentFile: TestFile = found; + const parentSuite = findParentSuite(parents); + return { + testFunction: node, + parentTestFile: parentFile, + parentTestSuite: parentSuite, + xmlClassName: parentSuite ? parentSuite.xmlName : '' + }; +} From 436e4b354c732e581696a679381e1909f0d31bb5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 5 Sep 2019 17:06:21 -0600 Subject: [PATCH 07/30] Add basic unit tests for XUnitParser.updateResultsFromXmlLogFile(). --- .../testing/common/xUnitParser.unit.test.ts | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/test/testing/common/xUnitParser.unit.test.ts diff --git a/src/test/testing/common/xUnitParser.unit.test.ts b/src/test/testing/common/xUnitParser.unit.test.ts new file mode 100644 index 000000000000..f1759cccc5c9 --- /dev/null +++ b/src/test/testing/common/xUnitParser.unit.test.ts @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:max-func-body-length + +'use strict'; + +import { expect } from 'chai'; +import * as typeMoq from 'typemoq'; +import { IFileSystem } from '../../../client/common/platform/types'; +import { + IXUnitParser, PassCalculationFormulae, Tests, TestStatus +} from '../../../client/testing/common/types'; +import { XUnitParser } from '../../../client/testing/common/xUnitParser'; +import { createResults } from '../helpers-declarative'; +import { TestItem } from '../helpers-nodes'; +import { createEmptyResults } from '../helpers-results'; + +suite('Testing - parse JUnit XML file', () => { + let parser: IXUnitParser; + let fs: typeMoq.IMock; + setup(() => { + fs = typeMoq.Mock.ofType(undefined, typeMoq.MockBehavior.Strict); + parser = new XUnitParser(fs.object); + }); + + function fixResult(node: TestItem, file: string, line: number) { + switch (node.status) { + case TestStatus.Pass: + node.passed = true; + break; + case TestStatus.Fail: + case TestStatus.Error: + node.passed = false; + break; + default: + node.passed = undefined; + } + node.file = file; + node.line = line; + } + + test('success with single passing test', async () => { + const tests = createResults(` + ./ + test_spam.py + + test_spam + `); + const expected = createResults(` + ./ + test_spam.py + + test_spam P 1.001 + `); + fixResult( + expected.testFunctions[0].testFunction, + 'test_spam.py', + 3 + ); + const filename = 'x/y/z/results.xml'; + fs.setup(f => f.readFile(filename)) + .returns(() => Promise.resolve(` + + + + + + `)); + + await parser.updateResultsFromXmlLogFile( + tests, + filename, + PassCalculationFormulae.pytest + ); + + expect(tests).to.deep.equal(expected); + fs.verifyAll(); + }); + + test('no discovered tests', async () => { + const tests: Tests = createEmptyResults(); + const expected: Tests = createEmptyResults(); + expected.summary.passed = 1; // That's a little strange... + const filename = 'x/y/z/results.xml'; + fs.setup(f => f.readFile(filename)) + .returns(() => Promise.resolve(` + + + + + + `)); + + await parser.updateResultsFromXmlLogFile( + tests, + filename, + PassCalculationFormulae.pytest + ); + + expect(tests).to.deep.equal(expected); + fs.verifyAll(); + }); + + test('no tests run', async () => { + const tests: Tests = createEmptyResults(); + const expected: Tests = createEmptyResults(); + const filename = 'x/y/z/results.xml'; + fs.setup(f => f.readFile(filename)) + .returns(() => Promise.resolve(` + + + + `)); + + await parser.updateResultsFromXmlLogFile( + tests, + filename, + PassCalculationFormulae.pytest + ); + + expect(tests).to.deep.equal(expected); + fs.verifyAll(); + }); + + // Missing tests: + // * simple pytest + // * simple nose + // * complex + // * error + // * failure + // * skipped + // * no clobber old if not matching +}); From b17675ae81abf6de64d3c81dbce679cfb13d5a94 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 5 Sep 2019 17:35:30 -0600 Subject: [PATCH 08/30] Factor out updateTests(). --- src/client/testing/common/xUnitParser.ts | 142 ++++++++++++----------- 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 95e0daefbfe5..959492032b1b 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -82,79 +82,87 @@ async function updateResultsFromXmlLogFile( //return reject(error); } - const testSuiteResult: TestSuiteResult = parserResult.testsuite; - tests.summary.errors = getSafeInt(testSuiteResult.$.errors); - tests.summary.failures = getSafeInt(testSuiteResult.$.failures); - tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); - const testCount = getSafeInt(testSuiteResult.$.tests); - - switch (passCalculationFormulae) { - case PassCalculationFormulae.pytest: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - case PassCalculationFormulae.nosetests: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - default: { - throw new Error('Unknown Test Pass Calculation'); - } - } + updateTests(tests, parserResult.testsuite, passCalculationFormulae); + }); +} - if (!Array.isArray(testSuiteResult.testcase)) { - return; +// Update "tests" with the given results. +function updateTests( + tests: Tests, + testSuiteResult: TestSuiteResult, + passCalculationFormulae: PassCalculationFormulae +) { + tests.summary.errors = getSafeInt(testSuiteResult.$.errors); + tests.summary.failures = getSafeInt(testSuiteResult.$.failures); + tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); + const testCount = getSafeInt(testSuiteResult.$.tests); + + switch (passCalculationFormulae) { + case PassCalculationFormulae.pytest: { + tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; + break; + } + case PassCalculationFormulae.nosetests: { + tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; + break; + } + default: { + throw new Error('Unknown Test Pass Calculation'); } + } - testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { - const xmlClassName = testcase.$.classname.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); - const result = tests.testFunctions.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === testcase.$.name); - if (!result) { - // Possible we're dealing with nosetests, where the file name isn't returned to us - // When dealing with nose tests - // It is possible to have a test file named x in two separate test sub directories and have same functions/classes - // And unforutnately xunit log doesn't ouput the filename - - // result = tests.testFunctions.find(fn => fn.testFunction.name === testcase.$.name && - // fn.parentTestSuite && fn.parentTestSuite.name === testcase.$.classname); - - // Look for failed file test - const fileTest = testcase.$.file && tests.testFiles.find(file => file.nameToRun === testcase.$.file); - if (fileTest && testcase.error) { - fileTest.status = TestStatus.Error; - fileTest.passed = false; - fileTest.message = testcase.error[0].$.message; - fileTest.traceback = testcase.error[0]._; - } - return; - } + if (!Array.isArray(testSuiteResult.testcase)) { + return; + } - result.testFunction.line = getSafeInt(testcase.$.line, null); - result.testFunction.file = testcase.$.file; - result.testFunction.time = parseFloat(testcase.$.time); - result.testFunction.passed = true; - result.testFunction.status = TestStatus.Pass; - - if (testcase.failure) { - result.testFunction.status = TestStatus.Fail; - result.testFunction.passed = false; - result.testFunction.message = testcase.failure[0].$.message; - result.testFunction.traceback = testcase.failure[0]._; + testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { + const xmlClassName = testcase.$.classname.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); + const result = tests.testFunctions.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === testcase.$.name); + if (!result) { + // Possible we're dealing with nosetests, where the file name isn't returned to us + // When dealing with nose tests + // It is possible to have a test file named x in two separate test sub directories and have same functions/classes + // And unforutnately xunit log doesn't ouput the filename + + // result = tests.testFunctions.find(fn => fn.testFunction.name === testcase.$.name && + // fn.parentTestSuite && fn.parentTestSuite.name === testcase.$.classname); + + // Look for failed file test + const fileTest = testcase.$.file && tests.testFiles.find(file => file.nameToRun === testcase.$.file); + if (fileTest && testcase.error) { + fileTest.status = TestStatus.Error; + fileTest.passed = false; + fileTest.message = testcase.error[0].$.message; + fileTest.traceback = testcase.error[0]._; } + return; + } - if (testcase.error) { - result.testFunction.status = TestStatus.Error; - result.testFunction.passed = false; - result.testFunction.message = testcase.error[0].$.message; - result.testFunction.traceback = testcase.error[0]._; - } + result.testFunction.line = getSafeInt(testcase.$.line, null); + result.testFunction.file = testcase.$.file; + result.testFunction.time = parseFloat(testcase.$.time); + result.testFunction.passed = true; + result.testFunction.status = TestStatus.Pass; + + if (testcase.failure) { + result.testFunction.status = TestStatus.Fail; + result.testFunction.passed = false; + result.testFunction.message = testcase.failure[0].$.message; + result.testFunction.traceback = testcase.failure[0]._; + } - if (testcase.skipped) { - result.testFunction.status = TestStatus.Skipped; - result.testFunction.passed = undefined; - result.testFunction.message = testcase.skipped[0].$.message; - result.testFunction.traceback = ''; - } - }); + if (testcase.error) { + result.testFunction.status = TestStatus.Error; + result.testFunction.passed = false; + result.testFunction.message = testcase.error[0].$.message; + result.testFunction.traceback = testcase.error[0]._; + } + + if (testcase.skipped) { + result.testFunction.status = TestStatus.Skipped; + result.testFunction.passed = undefined; + result.testFunction.message = testcase.skipped[0].$.message; + result.testFunction.traceback = ''; + } }); } From fbceaf4705856c2128638f9c203afd7477691c56 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 10:39:50 -0600 Subject: [PATCH 09/30] Make updateResultsFromXmlLogFile() properly async again. --- src/client/testing/common/xUnitParser.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 959492032b1b..5dd2fc8f27a7 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -76,13 +76,19 @@ async function updateResultsFromXmlLogFile( // tslint:disable-next-line:no-require-imports const xml2js = require('xml2js'); - xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { - if (error) { - throw error; - //return reject(error); - } - - updateTests(tests, parserResult.testsuite, passCalculationFormulae); + // tslint:disable-next-line:no-any + return new Promise((resolve, reject) => { + xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { + if (error) { + return reject(error); + } + try { + updateTests(tests, parserResult.testsuite, passCalculationFormulae); + } catch (err) { + return reject(err); + } + return resolve(); + }); }); } From 96b012b7003a576188ae3bb51db75333dc9c4051 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 10:55:34 -0600 Subject: [PATCH 10/30] Make XUnitParser.updateResultsFromXmlLogFile() explicitly async. --- src/client/testing/common/xUnitParser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 5dd2fc8f27a7..2acd666958e3 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -49,12 +49,12 @@ export class XUnitParser implements IXUnitParser { @inject(IFileSystem) private readonly fs: IFileSystem ) { } - public updateResultsFromXmlLogFile( + public async updateResultsFromXmlLogFile( tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae - ): Promise { - return updateResultsFromXmlLogFile( + ) { + await updateResultsFromXmlLogFile( this.fs, tests, outputXmlFile, From 0a3772221792f75f83d21e64c19d8b747d3aee87 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 10:57:20 -0600 Subject: [PATCH 11/30] Merge updateResultsFromXmlLogFile() into XUnitParser.updateResultsFromXmlLogFile(). --- src/client/testing/common/xUnitParser.ts | 56 +++++++++--------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 2acd666958e3..87277d45cc1c 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -49,47 +49,33 @@ export class XUnitParser implements IXUnitParser { @inject(IFileSystem) private readonly fs: IFileSystem ) { } + // Update "tests" with the results parsed from the given file. public async updateResultsFromXmlLogFile( tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae ) { - await updateResultsFromXmlLogFile( - this.fs, - tests, - outputXmlFile, - passCalculationFormulae - ); - } -} - -// Update "tests" with the results parsed from the given file. -async function updateResultsFromXmlLogFile( - fs: IFileSystem, - tests: Tests, - outputXmlFile: string, - passCalculationFormulae: PassCalculationFormulae -) { - const data = await fs.readFile(outputXmlFile); - // Un-comment this line to capture the results file for later use in tests: - //await fs.writeFile('/tmp/results.xml', data); - - // tslint:disable-next-line:no-require-imports - const xml2js = require('xml2js'); - // tslint:disable-next-line:no-any - return new Promise((resolve, reject) => { - xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { - if (error) { - return reject(error); - } - try { - updateTests(tests, parserResult.testsuite, passCalculationFormulae); - } catch (err) { - return reject(err); - } - return resolve(); + const data = await this.fs.readFile(outputXmlFile); + // Un-comment this line to capture the results file for later use in tests: + //await fs.writeFile('/tmp/results.xml', data); + + // tslint:disable-next-line:no-require-imports + const xml2js = require('xml2js'); + // tslint:disable-next-line:no-any + await new Promise((resolve, reject) => { + xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { + if (error) { + return reject(error); + } + try { + updateTests(tests, parserResult.testsuite, passCalculationFormulae); + } catch (err) { + return reject(err); + } + return resolve(); + }); }); - }); + } } // Update "tests" with the given results. From f5130040f3d71055ff0952b72d06ae977f0eda3b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 11:10:53 -0600 Subject: [PATCH 12/30] Handle passCalculationFormulae earlier. --- src/client/testing/common/xUnitParser.ts | 40 ++++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 87277d45cc1c..a971b3503cb1 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -1,6 +1,8 @@ import { inject, injectable } from 'inversify'; import { IFileSystem } from '../../common/platform/types'; -import { IXUnitParser, PassCalculationFormulae, Tests, TestStatus } from './types'; +import { + IXUnitParser, PassCalculationFormulae, Tests, TestStatus, TestSummary +} from './types'; type TestSuiteResult = { $: { @@ -55,6 +57,15 @@ export class XUnitParser implements IXUnitParser { outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae ) { + switch (passCalculationFormulae) { + case PassCalculationFormulae.pytest: + case PassCalculationFormulae.nosetests: + break; + default: { + throw new Error('Unknown Test Pass Calculation'); + } + } + const data = await this.fs.readFile(outputXmlFile); // Un-comment this line to capture the results file for later use in tests: //await fs.writeFile('/tmp/results.xml', data); @@ -68,7 +79,7 @@ export class XUnitParser implements IXUnitParser { return reject(error); } try { - updateTests(tests, parserResult.testsuite, passCalculationFormulae); + updateTests(tests, parserResult.testsuite); } catch (err) { return reject(err); } @@ -78,30 +89,25 @@ export class XUnitParser implements IXUnitParser { } } +// Set the number of passing tests given the total number. +function setPassing( + summary: TestSummary, + total: number +) { + summary.passed = total - summary.failures - summary.skipped - summary.errors; +} + // Update "tests" with the given results. function updateTests( tests: Tests, - testSuiteResult: TestSuiteResult, - passCalculationFormulae: PassCalculationFormulae + testSuiteResult: TestSuiteResult ) { tests.summary.errors = getSafeInt(testSuiteResult.$.errors); tests.summary.failures = getSafeInt(testSuiteResult.$.failures); tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); const testCount = getSafeInt(testSuiteResult.$.tests); - switch (passCalculationFormulae) { - case PassCalculationFormulae.pytest: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - case PassCalculationFormulae.nosetests: { - tests.summary.passed = testCount - tests.summary.failures - tests.summary.skipped - tests.summary.errors; - break; - } - default: { - throw new Error('Unknown Test Pass Calculation'); - } - } + setPassing(tests.summary, testCount); if (!Array.isArray(testSuiteResult.testcase)) { return; From 3e159d619cf1ed1e80b8c77186912e3047ca4188 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 11:25:20 -0600 Subject: [PATCH 13/30] Wrap the callback-oriented xml2js.parseString in an async function. --- src/client/testing/common/xUnitParser.ts | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index a971b3503cb1..d0c6fdad065d 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -70,23 +70,25 @@ export class XUnitParser implements IXUnitParser { // Un-comment this line to capture the results file for later use in tests: //await fs.writeFile('/tmp/results.xml', data); - // tslint:disable-next-line:no-require-imports - const xml2js = require('xml2js'); + const parserResult = await parseXML(data) as { testsuite: TestSuiteResult }; + updateTests(tests, parserResult.testsuite); + } +} + +// tslint:disable-next-line:no-any +async function parseXML(data: string): Promise { + // tslint:disable-next-line:no-require-imports + const xml2js = require('xml2js'); + // tslint:disable-next-line:no-any + return new Promise((resolve, reject) => { // tslint:disable-next-line:no-any - await new Promise((resolve, reject) => { - xml2js.parseString(data, (error: Error, parserResult: { testsuite: TestSuiteResult }) => { - if (error) { - return reject(error); - } - try { - updateTests(tests, parserResult.testsuite); - } catch (err) { - return reject(err); - } - return resolve(); - }); + xml2js.parseString(data, (error: Error, result: any) => { + if (error) { + return reject(error); + } + return resolve(result); }); - } + }); } // Set the number of passing tests given the total number. From c6d8ae5f40543cf078772e00132208181d4b749a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Sep 2019 14:06:43 -0600 Subject: [PATCH 14/30] Fix createFileResults() on Windows. --- src/test/testing/helpers-nodes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testing/helpers-nodes.ts b/src/test/testing/helpers-nodes.ts index 81a236530e2c..4332b1555771 100644 --- a/src/test/testing/helpers-nodes.ts +++ b/src/test/testing/helpers-nodes.ts @@ -59,7 +59,7 @@ export function createFileResults( if (!xmlName) { xmlName = filename .replace(/\.[^.]+$/, '') - .replace(RegExp(path.sep), '.') + .replace(/[\\\/]/, '.') .replace(/^[.\\\/]*/, ''); } return { From 176c95600b691e31c691a45c6eee4af391a80167 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 13:40:49 -0600 Subject: [PATCH 15/30] Factor out updateSummary(). --- src/client/testing/common/xUnitParser.ts | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index d0c6fdad065d..48c23612e6d8 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -75,6 +75,7 @@ export class XUnitParser implements IXUnitParser { } } +// An async wrapper around xml2js.parseString(). // tslint:disable-next-line:no-any async function parseXML(data: string): Promise { // tslint:disable-next-line:no-require-imports @@ -91,25 +92,12 @@ async function parseXML(data: string): Promise { }); } -// Set the number of passing tests given the total number. -function setPassing( - summary: TestSummary, - total: number -) { - summary.passed = total - summary.failures - summary.skipped - summary.errors; -} - // Update "tests" with the given results. function updateTests( tests: Tests, testSuiteResult: TestSuiteResult ) { - tests.summary.errors = getSafeInt(testSuiteResult.$.errors); - tests.summary.failures = getSafeInt(testSuiteResult.$.failures); - tests.summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); - const testCount = getSafeInt(testSuiteResult.$.tests); - - setPassing(tests.summary, testCount); + updateSummary(tests.summary, testSuiteResult); if (!Array.isArray(testSuiteResult.testcase)) { return; @@ -166,3 +154,15 @@ function updateTests( } }); } + +// Update the summary with the information in the given results. +function updateSummary( + summary: TestSummary, + testSuiteResult: TestSuiteResult +) { + summary.errors = getSafeInt(testSuiteResult.$.errors); + summary.failures = getSafeInt(testSuiteResult.$.failures); + summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); + const testCount = getSafeInt(testSuiteResult.$.tests); + summary.passed = testCount - summary.failures - summary.skipped - summary.errors; +} From 832cc2407bcc0e490b61a62f2f846d419cec94e3 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 13:42:51 -0600 Subject: [PATCH 16/30] Factor out findTestFunction(). --- src/client/testing/common/xUnitParser.ts | 63 ++++++++++++++++-------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 48c23612e6d8..8cb817372d36 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -1,7 +1,8 @@ import { inject, injectable } from 'inversify'; import { IFileSystem } from '../../common/platform/types'; import { - IXUnitParser, PassCalculationFormulae, Tests, TestStatus, TestSummary + FlattenedTestFunction, IXUnitParser, PassCalculationFormulae, + TestFunction, Tests, TestStatus, TestSummary } from './types'; type TestSuiteResult = { @@ -104,9 +105,12 @@ function updateTests( } testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { - const xmlClassName = testcase.$.classname.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); - const result = tests.testFunctions.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === testcase.$.name); - if (!result) { + const testFunc = findTestFunction( + tests.testFunctions, + testcase.$.classname, + testcase.$.name + ); + if (!testFunc) { // Possible we're dealing with nosetests, where the file name isn't returned to us // When dealing with nose tests // It is possible to have a test file named x in two separate test sub directories and have same functions/classes @@ -126,31 +130,31 @@ function updateTests( return; } - result.testFunction.line = getSafeInt(testcase.$.line, null); - result.testFunction.file = testcase.$.file; - result.testFunction.time = parseFloat(testcase.$.time); - result.testFunction.passed = true; - result.testFunction.status = TestStatus.Pass; + testFunc.line = getSafeInt(testcase.$.line, null); + testFunc.file = testcase.$.file; + testFunc.time = parseFloat(testcase.$.time); + testFunc.passed = true; + testFunc.status = TestStatus.Pass; if (testcase.failure) { - result.testFunction.status = TestStatus.Fail; - result.testFunction.passed = false; - result.testFunction.message = testcase.failure[0].$.message; - result.testFunction.traceback = testcase.failure[0]._; + testFunc.status = TestStatus.Fail; + testFunc.passed = false; + testFunc.message = testcase.failure[0].$.message; + testFunc.traceback = testcase.failure[0]._; } if (testcase.error) { - result.testFunction.status = TestStatus.Error; - result.testFunction.passed = false; - result.testFunction.message = testcase.error[0].$.message; - result.testFunction.traceback = testcase.error[0]._; + testFunc.status = TestStatus.Error; + testFunc.passed = false; + testFunc.message = testcase.error[0].$.message; + testFunc.traceback = testcase.error[0]._; } if (testcase.skipped) { - result.testFunction.status = TestStatus.Skipped; - result.testFunction.passed = undefined; - result.testFunction.message = testcase.skipped[0].$.message; - result.testFunction.traceback = ''; + testFunc.status = TestStatus.Skipped; + testFunc.passed = undefined; + testFunc.message = testcase.skipped[0].$.message; + testFunc.traceback = ''; } }); } @@ -166,3 +170,20 @@ function updateSummary( const testCount = getSafeInt(testSuiteResult.$.tests); summary.passed = testCount - summary.failures - summary.skipped - summary.errors; } + +function findTestFunction( + candidates: FlattenedTestFunction[], + className: string, + funcName: string +): TestFunction | undefined { + const xmlClassName = className + .replace(/\(\)/g, '') + .replace(/\.\./g, '.') + .replace(/\.\./g, '.') + .replace(/\.+$/, ''); + const flattened = candidates.find(fn => fn.xmlClassName === xmlClassName && fn.testFunction.name === funcName); + if (!flattened) { + return; + } + return flattened.testFunction; +} From 0f4a96ee07c2744ad651e556dcc66ded2d221c9b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 14:22:23 -0600 Subject: [PATCH 17/30] Factor out updateResultInfo() and updateResultStatus(). --- src/client/testing/common/xUnitParser.ts | 76 +++++++++++++----------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 8cb817372d36..4d7ba634240a 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -2,7 +2,7 @@ import { inject, injectable } from 'inversify'; import { IFileSystem } from '../../common/platform/types'; import { FlattenedTestFunction, IXUnitParser, PassCalculationFormulae, - TestFunction, Tests, TestStatus, TestSummary + TestFunction, TestResult, Tests, TestStatus, TestSummary } from './types'; type TestSuiteResult = { @@ -110,7 +110,10 @@ function updateTests( testcase.$.classname, testcase.$.name ); - if (!testFunc) { + if (testFunc) { + updateResultInfo(testFunc, testcase); + updateResultStatus(testFunc, testcase); + } else { // Possible we're dealing with nosetests, where the file name isn't returned to us // When dealing with nose tests // It is possible to have a test file named x in two separate test sub directories and have same functions/classes @@ -122,39 +125,8 @@ function updateTests( // Look for failed file test const fileTest = testcase.$.file && tests.testFiles.find(file => file.nameToRun === testcase.$.file); if (fileTest && testcase.error) { - fileTest.status = TestStatus.Error; - fileTest.passed = false; - fileTest.message = testcase.error[0].$.message; - fileTest.traceback = testcase.error[0]._; + updateResultStatus(fileTest, testcase); } - return; - } - - testFunc.line = getSafeInt(testcase.$.line, null); - testFunc.file = testcase.$.file; - testFunc.time = parseFloat(testcase.$.time); - testFunc.passed = true; - testFunc.status = TestStatus.Pass; - - if (testcase.failure) { - testFunc.status = TestStatus.Fail; - testFunc.passed = false; - testFunc.message = testcase.failure[0].$.message; - testFunc.traceback = testcase.failure[0]._; - } - - if (testcase.error) { - testFunc.status = TestStatus.Error; - testFunc.passed = false; - testFunc.message = testcase.error[0].$.message; - testFunc.traceback = testcase.error[0]._; - } - - if (testcase.skipped) { - testFunc.status = TestStatus.Skipped; - testFunc.passed = undefined; - testFunc.message = testcase.skipped[0].$.message; - testFunc.traceback = ''; } }); } @@ -187,3 +159,39 @@ function findTestFunction( } return flattened.testFunction; } + +function updateResultInfo( + result: TestResult, + testCase: TestCaseResult +) { + result.file = testCase.$.file; + result.line = getSafeInt(testCase.$.line, null); + result.time = parseFloat(testCase.$.time); +} + +function updateResultStatus( + result: TestResult, + testCase: TestCaseResult +) { + if (testCase.error) { + result.status = TestStatus.Error; + result.passed = false; + result.message = testCase.error[0].$.message; + result.traceback = testCase.error[0]._; + } else if (testCase.failure) { + result.status = TestStatus.Fail; + result.passed = false; + result.message = testCase.failure[0].$.message; + result.traceback = testCase.failure[0]._; + } else if (testCase.skipped) { + result.status = TestStatus.Skipped; + result.passed = undefined; + result.message = testCase.skipped[0].$.message; + result.traceback = ''; + } else { + result.status = TestStatus.Pass; + result.passed = true; + //result.message = undefined; + //result.traceback = undefined; + } +} From 019d0d9b86cabb2fbbe782f354017c887d2f2388 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 14:29:26 -0600 Subject: [PATCH 18/30] Add a comment about unknown tests. --- src/client/testing/common/xUnitParser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 4d7ba634240a..c27211fa8e06 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -104,6 +104,8 @@ function updateTests( return; } + // Update the results for each test. + // Previously unknown tests are ignored. testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { const testFunc = findTestFunction( tests.testFunctions, From 5959283133ca48f21487998493062a724b3a847d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 14:36:56 -0600 Subject: [PATCH 19/30] Link to the GH issue for missing tests. --- src/test/testing/common/xUnitParser.unit.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/testing/common/xUnitParser.unit.test.ts b/src/test/testing/common/xUnitParser.unit.test.ts index f1759cccc5c9..33d53f1ce482 100644 --- a/src/test/testing/common/xUnitParser.unit.test.ts +++ b/src/test/testing/common/xUnitParser.unit.test.ts @@ -123,7 +123,7 @@ suite('Testing - parse JUnit XML file', () => { fs.verifyAll(); }); - // Missing tests: + // Missing tests (see gh-7447): // * simple pytest // * simple nose // * complex @@ -131,4 +131,5 @@ suite('Testing - parse JUnit XML file', () => { // * failure // * skipped // * no clobber old if not matching + // * ... }); From 8f781bd2ea09b895d286402a59ea901a322ff043 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 14:52:26 -0600 Subject: [PATCH 20/30] Drop the commented-out util line. --- src/client/testing/common/xUnitParser.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index c27211fa8e06..340ad1148da9 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -68,8 +68,6 @@ export class XUnitParser implements IXUnitParser { } const data = await this.fs.readFile(outputXmlFile); - // Un-comment this line to capture the results file for later use in tests: - //await fs.writeFile('/tmp/results.xml', data); const parserResult = await parseXML(data) as { testsuite: TestSuiteResult }; updateTests(tests, parserResult.testsuite); From 9b667935a133a7e26e58189670fd2c9b81730aaf Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 17 Sep 2019 14:54:03 -0600 Subject: [PATCH 21/30] Use "await import" instead of "require". --- src/client/testing/common/xUnitParser.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index 340ad1148da9..c4a754789c7c 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -77,8 +77,7 @@ export class XUnitParser implements IXUnitParser { // An async wrapper around xml2js.parseString(). // tslint:disable-next-line:no-any async function parseXML(data: string): Promise { - // tslint:disable-next-line:no-require-imports - const xml2js = require('xml2js'); + const xml2js = await import('xml2js'); // tslint:disable-next-line:no-any return new Promise((resolve, reject) => { // tslint:disable-next-line:no-any From ab9c4a1000a37f86ab7b72092f8b5be0cc6a1fbd Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 13:02:01 -0600 Subject: [PATCH 22/30] Combine helpers-nodes and helpers-results. --- .../testing/common/xUnitParser.unit.test.ts | 5 +- src/test/testing/helpers-declarative.ts | 24 +- src/test/testing/helpers-nodes.ts | 233 ------------ src/test/testing/helpers-results.ts | 114 ------ src/test/testing/results.ts | 338 ++++++++++++++++++ 5 files changed, 350 insertions(+), 364 deletions(-) delete mode 100644 src/test/testing/helpers-nodes.ts delete mode 100644 src/test/testing/helpers-results.ts create mode 100644 src/test/testing/results.ts diff --git a/src/test/testing/common/xUnitParser.unit.test.ts b/src/test/testing/common/xUnitParser.unit.test.ts index 33d53f1ce482..e3a1c7dce117 100644 --- a/src/test/testing/common/xUnitParser.unit.test.ts +++ b/src/test/testing/common/xUnitParser.unit.test.ts @@ -13,8 +13,7 @@ import { } from '../../../client/testing/common/types'; import { XUnitParser } from '../../../client/testing/common/xUnitParser'; import { createResults } from '../helpers-declarative'; -import { TestItem } from '../helpers-nodes'; -import { createEmptyResults } from '../helpers-results'; +import { createEmptyResults, nodes } from '../results'; suite('Testing - parse JUnit XML file', () => { let parser: IXUnitParser; @@ -24,7 +23,7 @@ suite('Testing - parse JUnit XML file', () => { parser = new XUnitParser(fs.object); }); - function fixResult(node: TestItem, file: string, line: number) { + function fixResult(node: nodes.TestItem, file: string, line: number) { switch (node.status) { case TestStatus.Pass: node.passed = true; diff --git a/src/test/testing/helpers-declarative.ts b/src/test/testing/helpers-declarative.ts index a24caf88c446..a773ce920c69 100644 --- a/src/test/testing/helpers-declarative.ts +++ b/src/test/testing/helpers-declarative.ts @@ -12,12 +12,8 @@ import { getDedentedLines, getIndent, RESOURCE } from './helper'; import { - addDiscoveredFile, addDiscoveredSubFolder, addDiscoveredSuite, - addDiscoveredTest, createFolderResults, TestNode -} from './helpers-nodes'; -import { - createEmptyResults, flattenFunction, flattenSuite, updateSummary -} from './helpers-results'; + createEmptyResults, flattenFunction, flattenSuite, nodes, updateSummary +} from './results'; type ParsedTestNode = { indent: string; @@ -26,7 +22,7 @@ type ParsedTestNode = { result: TestResult; }; -type TestParent = TestNode & { +type TestParent = nodes.TestNode & { indent: string; }; @@ -47,10 +43,10 @@ export function createResults( } const parsed = parseTestLine(line); - let node: TestNode; + let node: nodes.TestNode; if (isRootNode(parsed)) { parents.length = 0; // Clear the array. - node = createFolderResults( + node = nodes.createFolderResults( parsed.name, undefined, resource @@ -241,13 +237,13 @@ function buildDiscoveredChildNode( testType: TestType, provider: TestProvider, resource?: Uri -): TestNode { +): nodes.TestNode { switch (testType) { case TestType.testFolder: if (parent.testType !== TestType.testFolder) { throw Error('parent must be a folder'); } - return addDiscoveredSubFolder( + return nodes.addDiscoveredSubFolder( parent as TestFolder, name, undefined, @@ -257,7 +253,7 @@ function buildDiscoveredChildNode( if (parent.testType !== TestType.testFolder) { throw Error('parent must be a folder'); } - return addDiscoveredFile( + return nodes.addDiscoveredFile( parent as TestFolder, name, undefined, @@ -273,7 +269,7 @@ function buildDiscoveredChildNode( } else { throw Error('parent must be a file or suite'); } - return addDiscoveredSuite( + return nodes.addDiscoveredSuite( suiteParent, name, undefined, @@ -293,7 +289,7 @@ function buildDiscoveredChildNode( } else { throw Error('parent must be a file, suite, or function'); } - return addDiscoveredTest( + return nodes.addDiscoveredTest( funcParent, name, undefined, diff --git a/src/test/testing/helpers-nodes.ts b/src/test/testing/helpers-nodes.ts deleted file mode 100644 index 4332b1555771..000000000000 --- a/src/test/testing/helpers-nodes.ts +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import { Uri } from 'vscode'; -import { - SubtestParent, TestFile, TestFolder, TestFunction, TestProvider, - TestStatus, TestSuite, TestType -} from '../../client/testing/common/types'; -import { fixPath, RESOURCE } from './helper'; - -type SuperTest = TestFunction & { - subtests: TestFunction[]; -}; - -export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; - -export type TestNode = TestItem & { - testType: TestType; -}; - -// Set the result-oriented properties back to their "unset" values. -export function resetResult(node: TestNode) { - node.time = 0; - node.status = TestStatus.Unknown; -} - -//******************************** -// builders for empty low-level test results - -export function createFolderResults( - dirname: string, - nameToRun?: string, - resource: Uri = RESOURCE -): TestNode { - dirname = fixPath(dirname); - return { - resource: resource, - name: dirname, - nameToRun: nameToRun || dirname, - folders: [], - testFiles: [], - testType: TestType.testFolder, - // result - time: 0, - status: TestStatus.Unknown - }; -} - -export function createFileResults( - filename: string, - nameToRun?: string, - xmlName?: string, - resource: Uri = RESOURCE -): TestNode { - filename = fixPath(filename); - if (!xmlName) { - xmlName = filename - .replace(/\.[^.]+$/, '') - .replace(/[\\\/]/, '.') - .replace(/^[.\\\/]*/, ''); - } - return { - resource: resource, - fullPath: filename, - name: path.basename(filename), - nameToRun: nameToRun || filename, - xmlName: xmlName!, - suites: [], - functions: [], - testType: TestType.testFile, - // result - time: 0, - status: TestStatus.Unknown - }; -} - -export function createSuiteResults( - name: string, - nameToRun?: string, - xmlName?: string, - provider: TestProvider = 'pytest', - isInstance: boolean = false, - resource: Uri = RESOURCE -): TestNode { - return { - resource: resource, - name: name, - nameToRun: nameToRun || '', // must be set for parent - xmlName: xmlName || '', // must be set for parent - isUnitTest: provider === 'unittest', - isInstance: isInstance, - suites: [], - functions: [], - testType: TestType.testSuite, - // result - time: 0, - status: TestStatus.Unknown - }; -} - -export function createTestResults( - name: string, - nameToRun?: string, - subtestParent?: SubtestParent, - resource: Uri = RESOURCE -): TestNode { - return { - resource: resource, - name: name, - nameToRun: nameToRun || name, - subtestParent: subtestParent, - testType: TestType.testFunction, - // result - time: 0, - status: TestStatus.Unknown - }; -} - -//******************************** -// adding children to low-level nodes - -export function addDiscoveredSubFolder( - parent: TestFolder, - basename: string, - nameToRun?: string, - resource?: Uri -): TestNode { - const dirname = path.join(parent.name, fixPath(basename)); - const subFolder = createFolderResults( - dirname, - nameToRun, - resource || parent.resource || RESOURCE - ); - parent.folders.push(subFolder as TestFolder); - return subFolder; -} - -export function addDiscoveredFile( - parent: TestFolder, - basename: string, - nameToRun?: string, - xmlName?: string, - resource?: Uri -): TestNode { - const filename = path.join(parent.name, fixPath(basename)); - const file = createFileResults( - filename, - nameToRun, - xmlName, - resource || parent.resource || RESOURCE - ); - parent.testFiles.push(file as TestFile); - return file; -} - -export function addDiscoveredSuite( - parent: TestFile | TestSuite, - name: string, - nameToRun?: string, - xmlName?: string, - provider: TestProvider = 'pytest', - isInstance?: boolean, - resource?: Uri -): TestNode { - if (!nameToRun) { - const sep = provider === 'pytest' ? '::' : '.'; - nameToRun = `${parent.nameToRun}${sep}${name}`; - } - const suite = createSuiteResults( - name, - nameToRun!, - xmlName || `${parent.xmlName}.${name}`, - provider, - isInstance, - resource || parent.resource || RESOURCE - ); - parent.suites.push(suite as TestSuite); - return suite; -} - -export function addDiscoveredTest( - parent: TestFile | TestSuite, - name: string, - nameToRun?: string, - provider: TestProvider = 'pytest', - resource?: Uri -): TestNode { - if (!nameToRun) { - const sep = provider === 'pytest' ? '::' : '.'; - nameToRun = `${parent.nameToRun}${sep}${name}`; - } - const test = createTestResults( - name, - nameToRun, - undefined, - resource || parent.resource || RESOURCE - ); - parent.functions.push(test as TestFunction); - return test; -} - -export function addDiscoveredSubtest( - parent: SuperTest, - name: string, - nameToRun?: string, - provider: TestProvider = 'pytest', - resource?: Uri -): TestNode { - const subtest = createTestResults( - name, - nameToRun!, - { - name: parent.name, - nameToRun: parent.nameToRun, - asSuite: createSuiteResults( - parent.name, - parent.nameToRun, - '', - provider, - false, - parent.resource - ) as TestSuite, - time: 0 - }, - resource || parent.resource || RESOURCE - ); - (subtest as TestFunction).subtestParent!.asSuite.functions.push(subtest); - parent.subtests.push(subtest as TestFunction); - return subtest; -} diff --git a/src/test/testing/helpers-results.ts b/src/test/testing/helpers-results.ts deleted file mode 100644 index 43ac2dd0a69f..000000000000 --- a/src/test/testing/helpers-results.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { - FlattenedTestFunction, FlattenedTestSuite, TestFile, TestFunction, - Tests, TestStatus, TestSuite, TestSummary, TestType -} from '../../client/testing/common/types'; -import { - TestNode -} from './helpers-nodes'; - -// Return an initialized test results. -export function createEmptyResults(): Tests { - return { - summary: { - passed: 0, - failures: 0, - errors: 0, - skipped: 0 - }, - testFiles: [], - testFunctions: [], - testSuites: [], - testFolders: [], - rootTestFolders: [] - }; -} - -// Increment the appropriate summary property. -export function updateSummary( - summary: TestSummary, - status: TestStatus -) { - switch (status) { - case TestStatus.Pass: - summary.passed += 1; - break; - case TestStatus.Fail: - summary.failures += 1; - break; - case TestStatus.Error: - summary.errors += 1; - break; - case TestStatus.Skipped: - summary.skipped += 1; - break; - default: - // Do not update the results. - } -} - -// Return the file found walking up the parents, if any. -// -// There should only be one parent file. -export function findParentFile(parents: TestNode[]): TestFile | undefined { - // Iterate in reverse order. - for (let i = parents.length; i > 0; i -= 1) { - const parent = parents[i - 1]; - if (parent.testType === TestType.testFile) { - return parent as TestFile; - } - } - return; -} - -// Return the first suite found walking up the parents, if any. -export function findParentSuite(parents: TestNode[]): TestSuite | undefined { - // Iterate in reverse order. - for (let i = parents.length; i > 0; i -= 1) { - const parent = parents[i - 1]; - if (parent.testType === TestType.testSuite) { - return parent as TestSuite; - } - } - return; -} - -// Return the "flattened" test suite node. -export function flattenSuite( - node: TestSuite, - parents: TestNode[] -): FlattenedTestSuite { - const found = findParentFile(parents); - if (!found) { - throw Error('parent file not found'); - } - const parentFile: TestFile = found; - return { - testSuite: node, - parentTestFile: parentFile, - xmlClassName: node.xmlName - }; -} - -// Return the "flattened" test function node. -export function flattenFunction( - node: TestFunction, - parents: TestNode[] -): FlattenedTestFunction { - const found = findParentFile(parents); - if (!found) { - throw Error('parent file not found'); - } - const parentFile: TestFile = found; - const parentSuite = findParentSuite(parents); - return { - testFunction: node, - parentTestFile: parentFile, - parentTestSuite: parentSuite, - xmlClassName: parentSuite ? parentSuite.xmlName : '' - }; -} diff --git a/src/test/testing/results.ts b/src/test/testing/results.ts new file mode 100644 index 000000000000..86ba589a5d5b --- /dev/null +++ b/src/test/testing/results.ts @@ -0,0 +1,338 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { + FlattenedTestFunction, FlattenedTestSuite, + SubtestParent, TestFile, TestFolder, TestFunction, TestProvider, + Tests, TestStatus, TestSuite, TestSummary, TestType +} from '../../client/testing/common/types'; +import { fixPath, RESOURCE } from './helper'; + +// Return an initialized test results. +export function createEmptyResults(): Tests { + return { + summary: { + passed: 0, + failures: 0, + errors: 0, + skipped: 0 + }, + testFiles: [], + testFunctions: [], + testSuites: [], + testFolders: [], + rootTestFolders: [] + }; +} + +// Increment the appropriate summary property. +export function updateSummary( + summary: TestSummary, + status: TestStatus +) { + switch (status) { + case TestStatus.Pass: + summary.passed += 1; + break; + case TestStatus.Fail: + summary.failures += 1; + break; + case TestStatus.Error: + summary.errors += 1; + break; + case TestStatus.Skipped: + summary.skipped += 1; + break; + default: + // Do not update the results. + } +} + +// Return the file found walking up the parents, if any. +// +// There should only be one parent file. +export function findParentFile(parents: nodes.TestNode[]): TestFile | undefined { + // Iterate in reverse order. + for (let i = parents.length; i > 0; i -= 1) { + const parent = parents[i - 1]; + if (parent.testType === TestType.testFile) { + return parent as TestFile; + } + } + return; +} + +// Return the first suite found walking up the parents, if any. +export function findParentSuite(parents: nodes.TestNode[]): TestSuite | undefined { + // Iterate in reverse order. + for (let i = parents.length; i > 0; i -= 1) { + const parent = parents[i - 1]; + if (parent.testType === TestType.testSuite) { + return parent as TestSuite; + } + } + return; +} + +// Return the "flattened" test suite node. +export function flattenSuite( + node: TestSuite, + parents: nodes.TestNode[] +): FlattenedTestSuite { + const found = findParentFile(parents); + if (!found) { + throw Error('parent file not found'); + } + const parentFile: TestFile = found; + return { + testSuite: node, + parentTestFile: parentFile, + xmlClassName: node.xmlName + }; +} + +// Return the "flattened" test function node. +export function flattenFunction( + node: TestFunction, + parents: nodes.TestNode[] +): FlattenedTestFunction { + const found = findParentFile(parents); + if (!found) { + throw Error('parent file not found'); + } + const parentFile: TestFile = found; + const parentSuite = findParentSuite(parents); + return { + testFunction: node, + parentTestFile: parentFile, + parentTestSuite: parentSuite, + xmlClassName: parentSuite ? parentSuite.xmlName : '' + }; +} + +// operations on raw test nodes +export namespace nodes { + type SuperTest = TestFunction & { + subtests: TestFunction[]; + }; + + export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; + + export type TestNode = TestItem & { + testType: TestType; + }; + // Set the result-oriented properties back to their "unset" values. + export function resetResult(node: TestNode) { + node.time = 0; + node.status = TestStatus.Unknown; + } + + //******************************** + // builders for empty low-level test results + + export function createFolderResults( + dirname: string, + nameToRun?: string, + resource: Uri = RESOURCE + ): TestNode { + dirname = fixPath(dirname); + return { + resource: resource, + name: dirname, + nameToRun: nameToRun || dirname, + folders: [], + testFiles: [], + testType: TestType.testFolder, + // result + time: 0, + status: TestStatus.Unknown + }; + } + + export function createFileResults( + filename: string, + nameToRun?: string, + xmlName?: string, + resource: Uri = RESOURCE + ): TestNode { + filename = fixPath(filename); + if (!xmlName) { + xmlName = filename + .replace(/\.[^.]+$/, '') + .replace(/[\\\/]/, '.') + .replace(/^[.\\\/]*/, ''); + } + return { + resource: resource, + fullPath: filename, + name: path.basename(filename), + nameToRun: nameToRun || filename, + xmlName: xmlName!, + suites: [], + functions: [], + testType: TestType.testFile, + // result + time: 0, + status: TestStatus.Unknown + }; + } + + export function createSuiteResults( + name: string, + nameToRun?: string, + xmlName?: string, + provider: TestProvider = 'pytest', + isInstance: boolean = false, + resource: Uri = RESOURCE + ): TestNode { + return { + resource: resource, + name: name, + nameToRun: nameToRun || '', // must be set for parent + xmlName: xmlName || '', // must be set for parent + isUnitTest: provider === 'unittest', + isInstance: isInstance, + suites: [], + functions: [], + testType: TestType.testSuite, + // result + time: 0, + status: TestStatus.Unknown + }; + } + + export function createTestResults( + name: string, + nameToRun?: string, + subtestParent?: SubtestParent, + resource: Uri = RESOURCE + ): TestNode { + return { + resource: resource, + name: name, + nameToRun: nameToRun || name, + subtestParent: subtestParent, + testType: TestType.testFunction, + // result + time: 0, + status: TestStatus.Unknown + }; + } + + //******************************** + // adding children to low-level nodes + + export function addDiscoveredSubFolder( + parent: TestFolder, + basename: string, + nameToRun?: string, + resource?: Uri + ): TestNode { + const dirname = path.join(parent.name, fixPath(basename)); + const subFolder = createFolderResults( + dirname, + nameToRun, + resource || parent.resource || RESOURCE + ); + parent.folders.push(subFolder as TestFolder); + return subFolder; + } + + export function addDiscoveredFile( + parent: TestFolder, + basename: string, + nameToRun?: string, + xmlName?: string, + resource?: Uri + ): TestNode { + const filename = path.join(parent.name, fixPath(basename)); + const file = createFileResults( + filename, + nameToRun, + xmlName, + resource || parent.resource || RESOURCE + ); + parent.testFiles.push(file as TestFile); + return file; + } + + export function addDiscoveredSuite( + parent: TestFile | TestSuite, + name: string, + nameToRun?: string, + xmlName?: string, + provider: TestProvider = 'pytest', + isInstance?: boolean, + resource?: Uri + ): TestNode { + if (!nameToRun) { + const sep = provider === 'pytest' ? '::' : '.'; + nameToRun = `${parent.nameToRun}${sep}${name}`; + } + const suite = createSuiteResults( + name, + nameToRun!, + xmlName || `${parent.xmlName}.${name}`, + provider, + isInstance, + resource || parent.resource || RESOURCE + ); + parent.suites.push(suite as TestSuite); + return suite; + } + + export function addDiscoveredTest( + parent: TestFile | TestSuite, + name: string, + nameToRun?: string, + provider: TestProvider = 'pytest', + resource?: Uri + ): TestNode { + if (!nameToRun) { + const sep = provider === 'pytest' ? '::' : '.'; + nameToRun = `${parent.nameToRun}${sep}${name}`; + } + const test = createTestResults( + name, + nameToRun, + undefined, + resource || parent.resource || RESOURCE + ); + parent.functions.push(test as TestFunction); + return test; + } + + export function addDiscoveredSubtest( + parent: SuperTest, + name: string, + nameToRun?: string, + provider: TestProvider = 'pytest', + resource?: Uri + ): TestNode { + const subtest = createTestResults( + name, + nameToRun!, + { + name: parent.name, + nameToRun: parent.nameToRun, + asSuite: createSuiteResults( + parent.name, + parent.nameToRun, + '', + provider, + false, + parent.resource + ) as TestSuite, + time: 0 + }, + resource || parent.resource || RESOURCE + ); + (subtest as TestFunction).subtestParent!.asSuite.functions.push(subtest); + parent.subtests.push(subtest as TestFunction); + return subtest; + } +} From b8a096788f1a7bcca63a787a998add74422b51a7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 13:32:08 -0600 Subject: [PATCH 23/30] Combine helpers-declarative and helpers-results. --- .../testing/common/xUnitParser.unit.test.ts | 9 +- src/test/testing/helpers-declarative.ts | 302 ---------------- src/test/testing/results.ts | 327 +++++++++++++++++- 3 files changed, 316 insertions(+), 322 deletions(-) delete mode 100644 src/test/testing/helpers-declarative.ts diff --git a/src/test/testing/common/xUnitParser.unit.test.ts b/src/test/testing/common/xUnitParser.unit.test.ts index e3a1c7dce117..1b96d722a6e2 100644 --- a/src/test/testing/common/xUnitParser.unit.test.ts +++ b/src/test/testing/common/xUnitParser.unit.test.ts @@ -12,8 +12,7 @@ import { IXUnitParser, PassCalculationFormulae, Tests, TestStatus } from '../../../client/testing/common/types'; import { XUnitParser } from '../../../client/testing/common/xUnitParser'; -import { createResults } from '../helpers-declarative'; -import { createEmptyResults, nodes } from '../results'; +import { createDeclaratively, createEmptyResults, TestItem } from '../results'; suite('Testing - parse JUnit XML file', () => { let parser: IXUnitParser; @@ -23,7 +22,7 @@ suite('Testing - parse JUnit XML file', () => { parser = new XUnitParser(fs.object); }); - function fixResult(node: nodes.TestItem, file: string, line: number) { + function fixResult(node: TestItem, file: string, line: number) { switch (node.status) { case TestStatus.Pass: node.passed = true; @@ -40,13 +39,13 @@ suite('Testing - parse JUnit XML file', () => { } test('success with single passing test', async () => { - const tests = createResults(` + const tests = createDeclaratively(` ./ test_spam.py test_spam `); - const expected = createResults(` + const expected = createDeclaratively(` ./ test_spam.py diff --git a/src/test/testing/helpers-declarative.ts b/src/test/testing/helpers-declarative.ts deleted file mode 100644 index a773ce920c69..000000000000 --- a/src/test/testing/helpers-declarative.ts +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { Uri } from 'vscode'; -import { - TestFile, TestFolder, TestFunction, TestProvider, TestResult, Tests, - TestStatus, TestSuite, TestType -} from '../../client/testing/common/types'; -import { - getDedentedLines, getIndent, RESOURCE -} from './helper'; -import { - createEmptyResults, flattenFunction, flattenSuite, nodes, updateSummary -} from './results'; - -type ParsedTestNode = { - indent: string; - name: string; - testType: TestType; - result: TestResult; -}; - -type TestParent = nodes.TestNode & { - indent: string; -}; - -// Return a test tree built from concise declarative text. -export function createResults( - text: string, - provider: TestProvider = 'pytest', - resource: Uri = RESOURCE -): Tests { - const tests = createEmptyResults(); - - // Build the tree (and populate the return value at the same time). - const parents: TestParent[] = []; - let prev: TestParent; - for (const line of getDedentedLines(text)) { - if (line.trim() === '') { - continue; - } - const parsed = parseTestLine(line); - - let node: nodes.TestNode; - if (isRootNode(parsed)) { - parents.length = 0; // Clear the array. - node = nodes.createFolderResults( - parsed.name, - undefined, - resource - ); - tests.rootTestFolders.push(node as TestFolder); - tests.testFolders.push(node as TestFolder); - } else { - const parent = setMatchingParent( - parents, - prev!, - parsed.indent - ); - node = buildDiscoveredChildNode( - parent, - parsed.name, - parsed.testType, - provider, - resource - ); - switch (parsed.testType) { - case TestType.testFolder: - tests.testFolders.push(node as TestFolder); - break; - case TestType.testFile: - tests.testFiles.push(node as TestFile); - break; - case TestType.testSuite: - tests.testSuites.push( - flattenSuite(node as TestSuite, parents) - ); - break; - case TestType.testFunction: - // This does not deal with subtests? - tests.testFunctions.push( - flattenFunction(node as TestFunction, parents) - ); - break; - default: - } - } - - // Set the result. - node.status = parsed.result.status; - node.time = parsed.result.time; - updateSummary(tests.summary, node.status!); - - // Prepare for the next line. - prev = node as TestParent; - prev.indent = parsed.indent; - } - - return tests; -} - -// Determine the kind, indent, and result info based on the line. -function parseTestLine(line: string): ParsedTestNode { - if (line.includes('\\')) { - throw Error('expected / as path separator (even on Windows)'); - } - - const indent = getIndent(line); - line = line.trim(); - - const parts = line.split(' '); - let name = parts.shift(); - if (!name) { - throw Error('missing name'); - } - - // Determine the type from the name. - let testType: TestType; - if (name.endsWith('/')) { - // folder - testType = TestType.testFolder; - while (name.endsWith('/')) { - name = name.slice(0, -1); - } - } else if (name.includes('.')) { - // file - if (name.includes('/')) { - throw Error('filename must not include directories'); - } - testType = TestType.testFile; - } else if (name.startsWith('<')) { - // suite - if (!name.endsWith('>')) { - throw Error('suite missing closing bracket'); - } - testType = TestType.testSuite; - name = name.slice(1, -1); - } else { - // test - testType = TestType.testFunction; - } - - // Parse the results. - const result: TestResult = { - time: 0 - }; - if (parts.length !== 0 && testType !== TestType.testFunction) { - throw Error('non-test nodes do not have results'); - } - switch (parts.length) { - case 0: - break; - case 1: - // tslint:disable-next-line:no-any - if (isNaN(parts[0] as any)) { - throw Error(`expected a time (float), got ${parts[0]}`); - } - result.time = parseFloat(parts[0]); - break; - case 2: - switch (parts[0]) { - case 'P': - result.status = TestStatus.Pass; - break; - case 'F': - result.status = TestStatus.Fail; - break; - case 'E': - result.status = TestStatus.Error; - break; - case 'S': - result.status = TestStatus.Skipped; - break; - default: - throw Error('expected a status and then a time'); - } - // tslint:disable-next-line:no-any - if (isNaN(parts[1] as any)) { - throw Error(`expected a time (float), got ${parts[1]}`); - } - result.time = parseFloat(parts[1]); - break; - default: - throw Error('too many items on line'); - } - - return { - indent: indent, - name: name, - testType: testType, - result: result - }; -} - -function isRootNode( - parsed: ParsedTestNode -): boolean { - if (parsed.indent === '') { - if (parsed.testType !== TestType.testFolder) { - throw Error('a top-level node must be a folder'); - } - return true; - } - return false; -} - -function setMatchingParent( - parents: TestParent[], - prev: TestParent, - parsedIndent: string -): TestParent { - let current = parents.length > 0 ? parents[parents.length - 1] : prev; - if (parsedIndent.length > current.indent.length) { - parents.push(prev); - current = prev; - } else { - while (parsedIndent !== current.indent) { - if (parsedIndent.length > current.indent.length) { - throw Error('mis-aligned indentation'); - } - - parents.pop(); - if (parents.length === 0) { - throw Error('mis-aligned indentation'); - } - current = parents[parents.length - 1]; - } - } - return current; -} - -function buildDiscoveredChildNode( - parent: TestParent, - name: string, - testType: TestType, - provider: TestProvider, - resource?: Uri -): nodes.TestNode { - switch (testType) { - case TestType.testFolder: - if (parent.testType !== TestType.testFolder) { - throw Error('parent must be a folder'); - } - return nodes.addDiscoveredSubFolder( - parent as TestFolder, - name, - undefined, - resource - ); - case TestType.testFile: - if (parent.testType !== TestType.testFolder) { - throw Error('parent must be a folder'); - } - return nodes.addDiscoveredFile( - parent as TestFolder, - name, - undefined, - undefined, - resource - ); - case TestType.testSuite: - let suiteParent: TestFile | TestSuite; - if (parent.testType === TestType.testFile) { - suiteParent = parent as TestFile; - } else if (parent.testType === TestType.testSuite) { - suiteParent = parent as TestSuite; - } else { - throw Error('parent must be a file or suite'); - } - return nodes.addDiscoveredSuite( - suiteParent, - name, - undefined, - undefined, - provider, - undefined, - resource - ); - case TestType.testFunction: - let funcParent: TestFile | TestSuite; - if (parent.testType === TestType.testFile) { - funcParent = parent as TestFile; - } else if (parent.testType === TestType.testSuite) { - funcParent = parent as TestSuite; - } else if (parent.testType === TestType.testFunction) { - throw Error('not finished: use addDiscoveredSubTest()'); - } else { - throw Error('parent must be a file, suite, or function'); - } - return nodes.addDiscoveredTest( - funcParent, - name, - undefined, - provider, - resource - ); - default: - throw Error('unsupported'); - } -} diff --git a/src/test/testing/results.ts b/src/test/testing/results.ts index 86ba589a5d5b..fd809293e1b6 100644 --- a/src/test/testing/results.ts +++ b/src/test/testing/results.ts @@ -8,9 +8,19 @@ import { Uri } from 'vscode'; import { FlattenedTestFunction, FlattenedTestSuite, SubtestParent, TestFile, TestFolder, TestFunction, TestProvider, - Tests, TestStatus, TestSuite, TestSummary, TestType + TestResult, Tests, TestStatus, TestSuite, TestSummary, TestType } from '../../client/testing/common/types'; -import { fixPath, RESOURCE } from './helper'; +import { fixPath, getDedentedLines, getIndent, RESOURCE } from './helper'; + +type SuperTest = TestFunction & { + subtests: TestFunction[]; +}; + +export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; + +export type TestNode = TestItem & { + testType: TestType; +}; // Return an initialized test results. export function createEmptyResults(): Tests { @@ -55,7 +65,7 @@ export function updateSummary( // Return the file found walking up the parents, if any. // // There should only be one parent file. -export function findParentFile(parents: nodes.TestNode[]): TestFile | undefined { +export function findParentFile(parents: TestNode[]): TestFile | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; @@ -67,7 +77,7 @@ export function findParentFile(parents: nodes.TestNode[]): TestFile | undefined } // Return the first suite found walking up the parents, if any. -export function findParentSuite(parents: nodes.TestNode[]): TestSuite | undefined { +export function findParentSuite(parents: TestNode[]): TestSuite | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; @@ -81,7 +91,7 @@ export function findParentSuite(parents: nodes.TestNode[]): TestSuite | undefine // Return the "flattened" test suite node. export function flattenSuite( node: TestSuite, - parents: nodes.TestNode[] + parents: TestNode[] ): FlattenedTestSuite { const found = findParentFile(parents); if (!found) { @@ -98,7 +108,7 @@ export function flattenSuite( // Return the "flattened" test function node. export function flattenFunction( node: TestFunction, - parents: nodes.TestNode[] + parents: TestNode[] ): FlattenedTestFunction { const found = findParentFile(parents); if (!found) { @@ -116,15 +126,6 @@ export function flattenFunction( // operations on raw test nodes export namespace nodes { - type SuperTest = TestFunction & { - subtests: TestFunction[]; - }; - - export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; - - export type TestNode = TestItem & { - testType: TestType; - }; // Set the result-oriented properties back to their "unset" values. export function resetResult(node: TestNode) { node.time = 0; @@ -336,3 +337,299 @@ export namespace nodes { return subtest; } } + +namespace declarative { + type TestParent = TestNode & { + indent: string; + }; + + type ParsedTestNode = { + indent: string; + name: string; + testType: TestType; + result: TestResult; + }; + + // Return a test tree built from concise declarative text. + export function parseResults( + text: string, + tests: Tests, + provider: TestProvider, + resource: Uri + ) { + // Build the tree (and populate the return value at the same time). + const parents: TestParent[] = []; + let prev: TestParent; + for (const line of getDedentedLines(text)) { + if (line.trim() === '') { + continue; + } + const parsed = parseTestLine(line); + + let node: TestNode; + if (isRootNode(parsed)) { + parents.length = 0; // Clear the array. + node = nodes.createFolderResults( + parsed.name, + undefined, + resource + ); + tests.rootTestFolders.push(node as TestFolder); + tests.testFolders.push(node as TestFolder); + } else { + const parent = setMatchingParent( + parents, + prev!, + parsed.indent + ); + node = buildDiscoveredChildNode( + parent, + parsed.name, + parsed.testType, + provider, + resource + ); + switch (parsed.testType) { + case TestType.testFolder: + tests.testFolders.push(node as TestFolder); + break; + case TestType.testFile: + tests.testFiles.push(node as TestFile); + break; + case TestType.testSuite: + tests.testSuites.push( + flattenSuite(node as TestSuite, parents) + ); + break; + case TestType.testFunction: + // This does not deal with subtests? + tests.testFunctions.push( + flattenFunction(node as TestFunction, parents) + ); + break; + default: + } + } + + // Set the result. + node.status = parsed.result.status; + node.time = parsed.result.time; + updateSummary(tests.summary, node.status!); + + // Prepare for the next line. + prev = node as TestParent; + prev.indent = parsed.indent; + } + } + + // Determine the kind, indent, and result info based on the line. + function parseTestLine(line: string): ParsedTestNode { + if (line.includes('\\')) { + throw Error('expected / as path separator (even on Windows)'); + } + + const indent = getIndent(line); + line = line.trim(); + + const parts = line.split(' '); + let name = parts.shift(); + if (!name) { + throw Error('missing name'); + } + + // Determine the type from the name. + let testType: TestType; + if (name.endsWith('/')) { + // folder + testType = TestType.testFolder; + while (name.endsWith('/')) { + name = name.slice(0, -1); + } + } else if (name.includes('.')) { + // file + if (name.includes('/')) { + throw Error('filename must not include directories'); + } + testType = TestType.testFile; + } else if (name.startsWith('<')) { + // suite + if (!name.endsWith('>')) { + throw Error('suite missing closing bracket'); + } + testType = TestType.testSuite; + name = name.slice(1, -1); + } else { + // test + testType = TestType.testFunction; + } + + // Parse the results. + const result: TestResult = { + time: 0 + }; + if (parts.length !== 0 && testType !== TestType.testFunction) { + throw Error('non-test nodes do not have results'); + } + switch (parts.length) { + case 0: + break; + case 1: + // tslint:disable-next-line:no-any + if (isNaN(parts[0] as any)) { + throw Error(`expected a time (float), got ${parts[0]}`); + } + result.time = parseFloat(parts[0]); + break; + case 2: + switch (parts[0]) { + case 'P': + result.status = TestStatus.Pass; + break; + case 'F': + result.status = TestStatus.Fail; + break; + case 'E': + result.status = TestStatus.Error; + break; + case 'S': + result.status = TestStatus.Skipped; + break; + default: + throw Error('expected a status and then a time'); + } + // tslint:disable-next-line:no-any + if (isNaN(parts[1] as any)) { + throw Error(`expected a time (float), got ${parts[1]}`); + } + result.time = parseFloat(parts[1]); + break; + default: + throw Error('too many items on line'); + } + + return { + indent: indent, + name: name, + testType: testType, + result: result + }; + } + + function isRootNode( + parsed: ParsedTestNode + ): boolean { + if (parsed.indent === '') { + if (parsed.testType !== TestType.testFolder) { + throw Error('a top-level node must be a folder'); + } + return true; + } + return false; + } + + function setMatchingParent( + parents: TestParent[], + prev: TestParent, + parsedIndent: string + ): TestParent { + let current = parents.length > 0 ? parents[parents.length - 1] : prev; + if (parsedIndent.length > current.indent.length) { + parents.push(prev); + current = prev; + } else { + while (parsedIndent !== current.indent) { + if (parsedIndent.length > current.indent.length) { + throw Error('mis-aligned indentation'); + } + + parents.pop(); + if (parents.length === 0) { + throw Error('mis-aligned indentation'); + } + current = parents[parents.length - 1]; + } + } + return current; + } + + function buildDiscoveredChildNode( + parent: TestParent, + name: string, + testType: TestType, + provider: TestProvider, + resource?: Uri + ): TestNode { + switch (testType) { + case TestType.testFolder: + if (parent.testType !== TestType.testFolder) { + throw Error('parent must be a folder'); + } + return nodes.addDiscoveredSubFolder( + parent as TestFolder, + name, + undefined, + resource + ); + case TestType.testFile: + if (parent.testType !== TestType.testFolder) { + throw Error('parent must be a folder'); + } + return nodes.addDiscoveredFile( + parent as TestFolder, + name, + undefined, + undefined, + resource + ); + case TestType.testSuite: + let suiteParent: TestFile | TestSuite; + if (parent.testType === TestType.testFile) { + suiteParent = parent as TestFile; + } else if (parent.testType === TestType.testSuite) { + suiteParent = parent as TestSuite; + } else { + throw Error('parent must be a file or suite'); + } + return nodes.addDiscoveredSuite( + suiteParent, + name, + undefined, + undefined, + provider, + undefined, + resource + ); + case TestType.testFunction: + let funcParent: TestFile | TestSuite; + if (parent.testType === TestType.testFile) { + funcParent = parent as TestFile; + } else if (parent.testType === TestType.testSuite) { + funcParent = parent as TestSuite; + } else if (parent.testType === TestType.testFunction) { + throw Error('not finished: use addDiscoveredSubTest()'); + } else { + throw Error('parent must be a file, suite, or function'); + } + return nodes.addDiscoveredTest( + funcParent, + name, + undefined, + provider, + resource + ); + default: + throw Error('unsupported'); + } + } +} + +// Return a test tree built from concise declarative text. +export function createDeclaratively( + text: string, + provider: TestProvider = 'pytest', + resource: Uri = RESOURCE +): Tests { + const tests = createEmptyResults(); + declarative.parseResults(text, tests, provider, resource); + return tests; +} From 8145887ec8e23b35c12d82d8cf2f050439af6fbe Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 13:40:07 -0600 Subject: [PATCH 24/30] Clean up testing common types. --- src/client/testing/common/types.ts | 165 ++++++++++++++--------------- 1 file changed, 80 insertions(+), 85 deletions(-) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 21f7d49ddafc..c256a9ba6da0 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -10,6 +10,14 @@ import { CommandSource } from './constants'; export type TestProvider = 'nosetest' | 'pytest' | 'unittest'; +export type UnitTestProduct = Product.nosetest | Product.pytest | Product.unittest; + +export type TestSettingsPropertyNames = { + enabledName: keyof ITestingSettings; + argsName: keyof ITestingSettings; + pathName?: keyof ITestingSettings; +}; + export type TestDiscoveryOptions = { workspaceFolder: Uri; cwd: string; @@ -32,13 +40,39 @@ export type TestRunOptions = { export type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; -export type TestFolder = TestResult & { - resource: Uri; - name: string; - testFiles: TestFile[]; - nameToRun: string; - folders: TestFolder[]; +export type LaunchOptions = { + cwd: string; + args: string[]; + testProvider: TestProvider; + token?: CancellationToken; + outChannel?: OutputChannel; }; + +export type ParserOptions = TestDiscoveryOptions; + +export type Options = { + workspaceFolder: Uri; + cwd: string; + args: string[]; + outChannel?: OutputChannel; + token: CancellationToken; +}; + +export enum PassCalculationFormulae { + pytest, + nosetests +} + +export type TestsToRun = { + testFolder?: TestFolder[]; + testFile?: TestFile[]; + testSuite?: TestSuite[]; + testFunction?: TestFunction[]; +}; + +//***************** +// test results + export enum TestType { testFile = 'testFile', testFolder = 'testFolder', @@ -46,6 +80,42 @@ export enum TestType { testFunction = 'testFunction', testWorkspaceFolder = 'testWorkspaceFolder' } + +export enum TestStatus { + Unknown = 'Unknown', + Discovering = 'Discovering', + Idle = 'Idle', + Running = 'Running', + Fail = 'Fail', + Error = 'Error', + Skipped = 'Skipped', + Pass = 'Pass' +} + +export type Node = { + expanded?: Boolean; +}; + +export type TestResult = Node & { + status?: TestStatus; + passed?: boolean; + time: number; + line?: number; + file?: string; + message?: string; + traceback?: string; + functionsPassed?: number; + functionsFailed?: number; + functionsDidNotRun?: number; +}; + +export type TestFolder = TestResult & { + resource: Uri; + name: string; + testFiles: TestFile[]; + nameToRun: string; + folders: TestFolder[]; +}; export type TestFile = TestResult & { resource: Uri; name: string; @@ -81,23 +151,6 @@ export type SubtestParent = TestResult & { asSuite: TestSuite; }; -export type TestResult = Node & { - status?: TestStatus; - passed?: boolean; - time: number; - line?: number; - file?: string; - message?: string; - traceback?: string; - functionsPassed?: number; - functionsFailed?: number; - functionsDidNotRun?: number; -}; - -export type Node = { - expanded?: Boolean; -}; - export type FlattenedTestFunction = { testFunction: TestFunction; parentTestSuite?: TestSuite; @@ -127,25 +180,8 @@ export type Tests = { rootTestFolders: TestFolder[]; }; -export enum TestStatus { - Unknown = 'Unknown', - Discovering = 'Discovering', - Idle = 'Idle', - Running = 'Running', - Fail = 'Fail', - Error = 'Error', - Skipped = 'Skipped', - Pass = 'Pass' -} - -export type TestsToRun = { - testFolder?: TestFolder[]; - testFile?: TestFile[]; - testSuite?: TestSuite[]; - testFunction?: TestFunction[]; -}; - -export type UnitTestProduct = Product.nosetest | Product.pytest | Product.unittest; +//***************** +// interfaces export interface ITestManagerService extends Disposable { getTestManager(): ITestManager | undefined; @@ -154,21 +190,13 @@ export interface ITestManagerService extends Disposable { } export const IWorkspaceTestManagerService = Symbol('IWorkspaceTestManagerService'); - export interface IWorkspaceTestManagerService extends Disposable { getTestManager(resource: Uri): ITestManager | undefined; getTestWorkingDirectory(resource: Uri): string; getPreferredTestManager(resource: Uri): UnitTestProduct | undefined; } -export type TestSettingsPropertyNames = { - enabledName: keyof ITestingSettings; - argsName: keyof ITestingSettings; - pathName?: keyof ITestingSettings; -}; - export const ITestsHelper = Symbol('ITestsHelper'); - export interface ITestsHelper { parseProviderName(product: UnitTestProduct): TestProvider; parseProduct(provider: TestProvider): UnitTestProduct; @@ -181,7 +209,6 @@ export interface ITestsHelper { } export const ITestVisitor = Symbol('ITestVisitor'); - export interface ITestVisitor { visitTestFunction(testFunction: TestFunction): void; visitTestSuite(testSuite: TestSuite): void; @@ -190,7 +217,6 @@ export interface ITestVisitor { } export const ITestCollectionStorageService = Symbol('ITestCollectionStorageService'); - export interface ITestCollectionStorageService extends Disposable { onDidChange: Event<{ uri: Uri; data?: TestDataItem }>; getTests(wkspace: Uri): Tests | undefined; @@ -201,34 +227,23 @@ export interface ITestCollectionStorageService extends Disposable { } export const ITestResultsService = Symbol('ITestResultsService'); - export interface ITestResultsService { resetResults(tests: Tests): void; updateResults(tests: Tests): void; } -export type LaunchOptions = { - cwd: string; - args: string[]; - testProvider: TestProvider; - token?: CancellationToken; - outChannel?: OutputChannel; -}; - export const ITestDebugLauncher = Symbol('ITestDebugLauncher'); - export interface ITestDebugLauncher { launchDebugger(options: LaunchOptions): Promise; } export const ITestManagerFactory = Symbol('ITestManagerFactory'); - export interface ITestManagerFactory extends Function { // tslint:disable-next-line:callable-types (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string): ITestManager; } -export const ITestManagerServiceFactory = Symbol('TestManagerServiceFactory'); +export const ITestManagerServiceFactory = Symbol('TestManagerServiceFactory'); export interface ITestManagerServiceFactory extends Function { // tslint:disable-next-line:callable-types (workspaceFolder: Uri): ITestManagerService; @@ -249,7 +264,6 @@ export interface ITestManager extends Disposable { } export const ITestDiscoveryService = Symbol('ITestDiscoveryService'); - export interface ITestDiscoveryService { discoverTests(options: TestDiscoveryOptions): Promise; } @@ -259,8 +273,6 @@ export interface ITestsParser { parse(content: string, options: ParserOptions): Tests; } -export type ParserOptions = TestDiscoveryOptions; - export const IUnitTestSocketServer = Symbol('IUnitTestSocketServer'); export interface IUnitTestSocketServer extends Disposable { on(event: string | symbol, listener: Function): this; @@ -270,35 +282,17 @@ export interface IUnitTestSocketServer extends Disposable { stop(): void; } -export type Options = { - workspaceFolder: Uri; - cwd: string; - args: string[]; - outChannel?: OutputChannel; - token: CancellationToken; -}; - export const ITestRunner = Symbol('ITestRunner'); export interface ITestRunner { run(testProvider: TestProvider, options: Options): Promise; } -export enum PassCalculationFormulae { - pytest, - nosetests -} - export const IXUnitParser = Symbol('IXUnitParser'); export interface IXUnitParser { // Update "tests" with the results parsed from the given file. updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise; } -export type PythonVersionInformation = { - major: number; - minor: number; -}; - export const ITestMessageService = Symbol('ITestMessageService'); export interface ITestMessageService { getFilteredTestMessages(rootDirectory: string, testResults: Tests): Promise; @@ -322,6 +316,7 @@ export interface ITestDebugConfig extends DebugConfiguration { justMyCode?: boolean; subProcess?: boolean; } + export const ITestContextService = Symbol('ITestContextService'); export interface ITestContextService extends Disposable { register(): void; From 4c8aafe4c122874cc3c019123c6808d86f0371f4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 13:48:52 -0600 Subject: [PATCH 25/30] Drop PassCalculationFormulae. --- src/client/testing/common/types.ts | 7 +------ src/client/testing/common/xUnitParser.ts | 14 ++----------- src/client/testing/nosetest/runner.ts | 7 +++++-- src/client/testing/pytest/runner.ts | 7 +++++-- .../testing/common/xUnitParser.unit.test.ts | 20 ++++--------------- .../pytest/pytest.testMessageService.test.ts | 4 ++-- 6 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index c256a9ba6da0..ba97264a737b 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -58,11 +58,6 @@ export type Options = { token: CancellationToken; }; -export enum PassCalculationFormulae { - pytest, - nosetests -} - export type TestsToRun = { testFolder?: TestFolder[]; testFile?: TestFile[]; @@ -290,7 +285,7 @@ export interface ITestRunner { export const IXUnitParser = Symbol('IXUnitParser'); export interface IXUnitParser { // Update "tests" with the results parsed from the given file. - updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise; + updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string): Promise; } export const ITestMessageService = Symbol('ITestMessageService'); diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index c4a754789c7c..a651b460d724 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -1,7 +1,7 @@ import { inject, injectable } from 'inversify'; import { IFileSystem } from '../../common/platform/types'; import { - FlattenedTestFunction, IXUnitParser, PassCalculationFormulae, + FlattenedTestFunction, IXUnitParser, TestFunction, TestResult, Tests, TestStatus, TestSummary } from './types'; @@ -55,18 +55,8 @@ export class XUnitParser implements IXUnitParser { // Update "tests" with the results parsed from the given file. public async updateResultsFromXmlLogFile( tests: Tests, - outputXmlFile: string, - passCalculationFormulae: PassCalculationFormulae + outputXmlFile: string ) { - switch (passCalculationFormulae) { - case PassCalculationFormulae.pytest: - case PassCalculationFormulae.nosetests: - break; - default: { - throw new Error('Unknown Test Pass Calculation'); - } - } - const data = await this.fs.readFile(outputXmlFile); const parserResult = await parseXML(data) as { testsuite: TestSuiteResult }; diff --git a/src/client/testing/nosetest/runner.ts b/src/client/testing/nosetest/runner.ts index 692e038aca8b..0f2dfa5fc845 100644 --- a/src/client/testing/nosetest/runner.ts +++ b/src/client/testing/nosetest/runner.ts @@ -6,7 +6,10 @@ import { noop } from '../../common/utils/misc'; import { IServiceContainer } from '../../ioc/types'; import { NOSETEST_PROVIDER } from '../common/constants'; import { Options } from '../common/runner'; -import { ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, IXUnitParser, LaunchOptions, PassCalculationFormulae, TestRunOptions, Tests } from '../common/types'; +import { + ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, + IXUnitParser, LaunchOptions, TestRunOptions, Tests +} from '../common/types'; import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; const WITH_XUNIT = '--with-xunit'; @@ -84,7 +87,7 @@ export class TestManagerRunner implements ITestManagerRunner { } private async updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { - await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.nosetests); + await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile); testResultsService.updateResults(tests); return tests; } diff --git a/src/client/testing/pytest/runner.ts b/src/client/testing/pytest/runner.ts index f10784011273..09014b150215 100644 --- a/src/client/testing/pytest/runner.ts +++ b/src/client/testing/pytest/runner.ts @@ -5,7 +5,10 @@ import { noop } from '../../common/utils/misc'; import { IServiceContainer } from '../../ioc/types'; import { PYTEST_PROVIDER } from '../common/constants'; import { Options } from '../common/runner'; -import { ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, IXUnitParser, LaunchOptions, PassCalculationFormulae, TestRunOptions, Tests } from '../common/types'; +import { + ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, + IXUnitParser, LaunchOptions, TestRunOptions, Tests +} from '../common/types'; import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; const JunitXmlArg = '--junitxml'; @@ -76,7 +79,7 @@ export class TestManagerRunner implements ITestManagerRunner { } private async updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { - await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.pytest); + await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile); testResultsService.updateResults(tests); return tests; } diff --git a/src/test/testing/common/xUnitParser.unit.test.ts b/src/test/testing/common/xUnitParser.unit.test.ts index 1b96d722a6e2..9a9b5498b4ca 100644 --- a/src/test/testing/common/xUnitParser.unit.test.ts +++ b/src/test/testing/common/xUnitParser.unit.test.ts @@ -9,7 +9,7 @@ import { expect } from 'chai'; import * as typeMoq from 'typemoq'; import { IFileSystem } from '../../../client/common/platform/types'; import { - IXUnitParser, PassCalculationFormulae, Tests, TestStatus + IXUnitParser, Tests, TestStatus } from '../../../client/testing/common/types'; import { XUnitParser } from '../../../client/testing/common/xUnitParser'; import { createDeclaratively, createEmptyResults, TestItem } from '../results'; @@ -66,11 +66,7 @@ suite('Testing - parse JUnit XML file', () => { `)); - await parser.updateResultsFromXmlLogFile( - tests, - filename, - PassCalculationFormulae.pytest - ); + await parser.updateResultsFromXmlLogFile(tests, filename); expect(tests).to.deep.equal(expected); fs.verifyAll(); @@ -90,11 +86,7 @@ suite('Testing - parse JUnit XML file', () => { `)); - await parser.updateResultsFromXmlLogFile( - tests, - filename, - PassCalculationFormulae.pytest - ); + await parser.updateResultsFromXmlLogFile(tests, filename); expect(tests).to.deep.equal(expected); fs.verifyAll(); @@ -111,11 +103,7 @@ suite('Testing - parse JUnit XML file', () => { `)); - await parser.updateResultsFromXmlLogFile( - tests, - filename, - PassCalculationFormulae.pytest - ); + await parser.updateResultsFromXmlLogFile(tests, filename); expect(tests).to.deep.equal(expected); fs.verifyAll(); diff --git a/src/test/testing/pytest/pytest.testMessageService.test.ts b/src/test/testing/pytest/pytest.testMessageService.test.ts index 2e573cedd97c..a745a8a0c0d2 100644 --- a/src/test/testing/pytest/pytest.testMessageService.test.ts +++ b/src/test/testing/pytest/pytest.testMessageService.test.ts @@ -21,7 +21,7 @@ import { CondaService } from '../../../client/interpreter/locators/services/cond import { TestDiscoveredTestParser } from '../../../client/testing/common/services/discoveredTestParser'; import { TestResultsService } from '../../../client/testing/common/services/testResultsService'; import { DiscoveredTests } from '../../../client/testing/common/services/types'; -import { ITestVisitor, PassCalculationFormulae, TestDiscoveryOptions, Tests, TestStatus } from '../../../client/testing/common/types'; +import { ITestVisitor, TestDiscoveryOptions, Tests, TestStatus } from '../../../client/testing/common/types'; import { XUnitParser } from '../../../client/testing/common/xUnitParser'; import { TestMessageService } from '../../../client/testing/pytest/services/testMessageService'; import { ILocationStackFrameDetails, IPythonTestMessage, PythonTestMessageSeverity } from '../../../client/testing/types'; @@ -146,7 +146,7 @@ suite('Unit Tests - PyTest - TestMessageService', () => { options.workspaceFolder = vscode.Uri.file(discoveredTest[0].root); const parsedTests: Tests = parser.parse(options.workspaceFolder, discoveredTest); const xUnitParser = new XUnitParser(filesystem); - await xUnitParser.updateResultsFromXmlLogFile(parsedTests, path.join(PYTEST_RESULTS_PATH, scenario.runOutput), PassCalculationFormulae.pytest); + await xUnitParser.updateResultsFromXmlLogFile(parsedTests, path.join(PYTEST_RESULTS_PATH, scenario.runOutput)); const testResultsService = new TestResultsService(testVisitor.object); testResultsService.updateResults(parsedTests); const testMessageService = new TestMessageService(ioc.serviceContainer); From bed432c47612d11f7bf71e365ef3b845034e0d86 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 13:55:16 -0600 Subject: [PATCH 26/30] Drop Node. --- src/client/testing/common/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index ba97264a737b..030bf7739f1f 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -87,11 +87,7 @@ export enum TestStatus { Pass = 'Pass' } -export type Node = { - expanded?: Boolean; -}; - -export type TestResult = Node & { +export type TestResult = { status?: TestStatus; passed?: boolean; time: number; From ef054bdfbffad809e7a18e2b586e1f2e45078206 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 14:52:55 -0600 Subject: [PATCH 27/30] TestType -> TestDataItemType --- .../common/services/discoveredTestParser.ts | 22 +- .../common/services/testResultsService.ts | 10 +- src/client/testing/common/testUtils.ts | 41 ++- src/client/testing/common/types.ts | 9 +- .../testing/explorer/commandHandlers.ts | 27 +- .../testing/explorer/failedTestHandler.ts | 8 +- .../testing/explorer/testTreeViewItem.ts | 24 +- .../testing/explorer/testTreeViewProvider.ts | 11 +- src/client/testing/types.ts | 7 + .../services/storageService.unit.test.ts | 34 +-- .../services/testResultsService.unit.test.ts | 58 +++-- .../services/testStatusService.unit.test.ts | 46 ++-- .../testing/common/testUtils.unit.test.ts | 234 +++++++++--------- .../explorer/testTreeViewItem.unit.test.ts | 12 +- .../testTreeViewProvider.unit.test.ts | 24 +- src/test/testing/results.ts | 54 ++-- .../testing/unittest/unittest.unit.test.ts | 48 ++-- 17 files changed, 346 insertions(+), 323 deletions(-) diff --git a/src/client/testing/common/services/discoveredTestParser.ts b/src/client/testing/common/services/discoveredTestParser.ts index 308ce17c5507..971dc25f789a 100644 --- a/src/client/testing/common/services/discoveredTestParser.ts +++ b/src/client/testing/common/services/discoveredTestParser.ts @@ -8,8 +8,8 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { IWorkspaceService } from '../../../common/application/types'; import { traceError } from '../../../common/logger'; -import { TestDataItem } from '../../types'; -import { getParentFile, getParentSuite, getTestType } from '../testUtils'; +import { TestDataItem, TestDataItemType } from '../../types'; +import { getParentFile, getParentSuite, getTestDataItemType } from '../testUtils'; import * as testing from '../types'; import * as discovery from './types'; @@ -74,17 +74,17 @@ export class TestDiscoveredTestParser implements discovery.ITestDiscoveredTestPa discoveredTests: discovery.DiscoveredTests, tests: testing.Tests ) { - const parentType = getTestType(parent); + const parentType = getTestDataItemType(parent); switch (parentType) { - case testing.TestType.testFolder: { + case TestDataItemType.folder: { this.processFolder(rootFolder, parent as testing.TestFolder, discoveredTests, tests); break; } - case testing.TestType.testFile: { + case TestDataItemType.file: { this.processFile(rootFolder, parent as testing.TestFile, discoveredTests, tests); break; } - case testing.TestType.testSuite: { + case TestDataItemType.suite: { this.processSuite(rootFolder, parent as testing.TestSuite, discoveredTests, tests); break; } @@ -383,11 +383,11 @@ function createFlattenedParameterizedFunction( func: testing.TestFunction, parent: testing.TestFile | testing.TestSuite ): testing.FlattenedTestFunction { - const type = getTestType(parent); - const parentFile = (type && type === testing.TestType.testSuite) ? + const type = getTestDataItemType(parent); + const parentFile = (type && type === TestDataItemType.suite) ? getParentFile(tests, func) : parent as testing.TestFile; - const parentSuite = (type && type === testing.TestType.testSuite) ? + const parentSuite = (type && type === TestDataItemType.suite) ? parent as testing.TestSuite : undefined; return { @@ -403,8 +403,8 @@ function createFlattenedFunction( func: testing.TestFunction ): testing.FlattenedTestFunction { const parent = getParentFile(tests, func); - const type = parent ? getTestType(parent) : undefined; - const parentFile = (type && type === testing.TestType.testSuite) ? + const type = parent ? getTestDataItemType(parent) : undefined; + const parentFile = (type && type === TestDataItemType.suite) ? getParentFile(tests, func) : parent as testing.TestFile; const parentSuite = getParentSuite(tests, func); diff --git a/src/client/testing/common/services/testResultsService.ts b/src/client/testing/common/services/testResultsService.ts index caf9fa586009..a0e9edc9982d 100644 --- a/src/client/testing/common/services/testResultsService.ts +++ b/src/client/testing/common/services/testResultsService.ts @@ -1,7 +1,7 @@ import { inject, injectable, named } from 'inversify'; -import { TestDataItem } from '../../types'; -import { getChildren, getTestType } from '../testUtils'; -import { ITestResultsService, ITestVisitor, Tests, TestStatus, TestType } from './../types'; +import { TestDataItem, TestDataItemType } from '../../types'; +import { getChildren, getTestDataItemType } from '../testUtils'; +import { ITestResultsService, ITestVisitor, Tests, TestStatus } from '../types'; @injectable() export class TestResultsService implements ITestResultsService { @@ -33,7 +33,7 @@ export class TestResultsService implements ITestResultsService { } } private updateTestItem(test: TestDataItem): void { - if (getTestType(test) === TestType.testFunction) { + if (getTestDataItemType(test) === TestDataItemType.function) { return; } let allChildrenPassed = true; @@ -42,7 +42,7 @@ export class TestResultsService implements ITestResultsService { const children = getChildren(test); children.forEach(child => { - if (getTestType(child) === TestType.testFunction) { + if (getTestDataItemType(child) === TestDataItemType.function) { if (typeof child.passed === 'boolean') { noChildrenRan = false; if (child.passed) { diff --git a/src/client/testing/common/testUtils.ts b/src/client/testing/common/testUtils.ts index ed5b1cc403af..185300838336 100644 --- a/src/client/testing/common/testUtils.ts +++ b/src/client/testing/common/testUtils.ts @@ -5,7 +5,7 @@ import { IApplicationShell, ICommandManager } from '../../common/application/typ import * as constants from '../../common/constants'; import { ITestingSettings, Product } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { TestDataItem, TestWorkspaceFolder } from '../types'; +import { TestDataItem, TestDataItemType, TestWorkspaceFolder } from '../types'; import { CommandSource } from './constants'; import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor'; import { @@ -21,7 +21,6 @@ import { TestSettingsPropertyNames, TestsToRun, TestSuite, - TestType, UnitTestProduct } from './types'; @@ -244,21 +243,21 @@ export class TestsHelper implements ITestsHelper { } } -export function getTestType(test: TestDataItem): TestType { +export function getTestDataItemType(test: TestDataItem): TestDataItemType { if (test instanceof TestWorkspaceFolder) { - return TestType.testWorkspaceFolder; + return TestDataItemType.workspaceFolder; } if (getTestFile(test)) { - return TestType.testFile; + return TestDataItemType.file; } if (getTestFolder(test)) { - return TestType.testFolder; + return TestDataItemType.folder; } if (getTestSuite(test)) { - return TestType.testSuite; + return TestDataItemType.suite; } if (getTestFunction(test)) { - return TestType.testFunction; + return TestDataItemType.function; } throw new Error('Unknown test type'); } @@ -305,14 +304,14 @@ export function getTestFunction(test: TestDataItem): TestFunction | undefined { * @returns {(TestDataItem | undefined)} */ export function getParent(tests: Tests, data: TestDataItem): TestDataItem | undefined { - switch (getTestType(data)) { - case TestType.testFile: { + switch (getTestDataItemType(data)) { + case TestDataItemType.file: { return getParentTestFolderForFile(tests, data as TestFile); } - case TestType.testFolder: { + case TestDataItemType.folder: { return getParentTestFolder(tests, data as TestFolder); } - case TestType.testSuite: { + case TestDataItemType.suite: { const suite = data as TestSuite; if (isSubtestsParent(suite)) { const fn = suite.functions[0]; @@ -328,7 +327,7 @@ export function getParent(tests: Tests, data: TestDataItem): TestDataItem | unde } return tests.testFiles.find(item => item.suites.indexOf(suite) >= 0); } - case TestType.testFunction: { + case TestDataItemType.function: { const fn = data as TestFunction; if (fn.subtestParent) { return fn.subtestParent.asSuite; @@ -354,7 +353,7 @@ export function getParent(tests: Tests, data: TestDataItem): TestDataItem | unde * @returns {(TestFolder | undefined)} */ function getParentTestFolder(tests: Tests, item: TestFolder | TestFile): TestFolder | undefined { - if (getTestType(item) === TestType.testFolder) { + if (getTestDataItemType(item) === TestDataItemType.folder) { return getParentTestFolderForFolder(tests, item as TestFolder); } return getParentTestFolderForFile(tests, item as TestFile); @@ -370,7 +369,7 @@ function getParentTestFolder(tests: Tests, item: TestFolder | TestFile): TestFol export function getParentFile(tests: Tests, suite: TestSuite | TestFunction): TestFile { let parent = getParent(tests, suite); while (parent) { - if (getTestType(parent) === TestType.testFile) { + if (getTestDataItemType(parent) === TestDataItemType.file) { return parent as TestFile; } parent = getParent(tests, parent); @@ -387,7 +386,7 @@ export function getParentFile(tests: Tests, suite: TestSuite | TestFunction): Te export function getParentSuite(tests: Tests, suite: TestSuite | TestFunction): TestSuite | undefined { let parent = getParent(tests, suite); while (parent) { - if (getTestType(parent) === TestType.testSuite) { + if (getTestDataItemType(parent) === TestDataItemType.suite) { return parent as TestSuite; } parent = getParent(tests, parent); @@ -453,14 +452,14 @@ export function findFlattendTestSuite(tests: Tests, suite: TestSuite): Flattened * @returns {TestDataItem[]} */ export function getChildren(item: TestDataItem): TestDataItem[] { - switch (getTestType(item)) { - case TestType.testFolder: { + switch (getTestDataItemType(item)) { + case TestDataItemType.folder: { return [ ...(item as TestFolder).folders, ...(item as TestFolder).testFiles ]; } - case TestType.testFile: { + case TestDataItemType.file: { const [subSuites, functions] = divideSubtests((item as TestFile).functions); return [ ...functions, @@ -468,7 +467,7 @@ export function getChildren(item: TestDataItem): TestDataItem[] { ...subSuites ]; } - case TestType.testSuite: { + case TestDataItemType.suite: { let subSuites: TestSuite[] = []; let functions = (item as TestSuite).functions; if (!isSubtestsParent((item as TestSuite))) { @@ -480,7 +479,7 @@ export function getChildren(item: TestDataItem): TestDataItem[] { ...subSuites ]; } - case TestType.testFunction: { + case TestDataItemType.function: { return []; } default: { diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 030bf7739f1f..440a385af05e 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -69,11 +69,10 @@ export type TestsToRun = { // test results export enum TestType { - testFile = 'testFile', - testFolder = 'testFolder', - testSuite = 'testSuite', - testFunction = 'testFunction', - testWorkspaceFolder = 'testWorkspaceFolder' + folder = 'testFolder', + file = 'testFile', + suite = 'testSuite', + function = 'testFunction' } export enum TestStatus { diff --git a/src/client/testing/explorer/commandHandlers.ts b/src/client/testing/explorer/commandHandlers.ts index 66b5ee58d225..1b7d6d0e90dd 100644 --- a/src/client/testing/explorer/commandHandlers.ts +++ b/src/client/testing/explorer/commandHandlers.ts @@ -10,19 +10,18 @@ import { traceDecorators } from '../../common/logger'; import { IDisposable } from '../../common/types'; import { swallowExceptions } from '../../common/utils/decorators'; import { CommandSource } from '../common/constants'; -import { getTestType } from '../common/testUtils'; +import { getTestDataItemType } from '../common/testUtils'; import { - TestFile, TestFolder, TestFunction, - TestsToRun, TestSuite, TestType + TestFile, TestFolder, TestFunction, TestsToRun, TestSuite } from '../common/types'; import { ITestExplorerCommandHandler } from '../navigation/types'; -import { ITestDataItemResource, TestDataItem } from '../types'; +import { ITestDataItemResource, TestDataItem, TestDataItemType } from '../types'; type NavigationCommands = typeof Commands.navigateToTestFile | typeof Commands.navigateToTestFunction | typeof Commands.navigateToTestSuite; const testNavigationCommandMapping: { [key: string]: NavigationCommands } = { - [TestType.testFile]: Commands.navigateToTestFile, - [TestType.testFunction]: Commands.navigateToTestFunction, - [TestType.testSuite]: Commands.navigateToTestSuite + [TestDataItemType.file]: Commands.navigateToTestFile, + [TestDataItemType.function]: Commands.navigateToTestFunction, + [TestDataItemType.suite]: Commands.navigateToTestSuite }; @injectable() @@ -53,8 +52,8 @@ export class TestExplorerCommandHandler implements ITestExplorerCommandHandler { @swallowExceptions('Open test node in Editor') @traceDecorators.error('Open test node in editor failed') protected async onOpenTestNodeInEditor(item: TestDataItem): Promise { - const testType = getTestType(item); - if (testType === TestType.testFolder) { + const testType = getTestDataItemType(item); + if (testType === TestDataItemType.folder) { throw new Error('Unknown Test Type'); } const command = testNavigationCommandMapping[testType]; @@ -68,20 +67,20 @@ export class TestExplorerCommandHandler implements ITestExplorerCommandHandler { protected async runDebugTestNode(item: TestDataItem, runType: 'run' | 'debug'): Promise { let testToRun: TestsToRun; - switch (getTestType(item)) { - case TestType.testFile: { + switch (getTestDataItemType(item)) { + case TestDataItemType.file: { testToRun = { testFile: [item as TestFile] }; break; } - case TestType.testFolder: { + case TestDataItemType.folder: { testToRun = { testFolder: [item as TestFolder] }; break; } - case TestType.testSuite: { + case TestDataItemType.suite: { testToRun = { testSuite: [item as TestSuite] }; break; } - case TestType.testFunction: { + case TestDataItemType.function: { testToRun = { testFunction: [item as TestFunction] }; break; } diff --git a/src/client/testing/explorer/failedTestHandler.ts b/src/client/testing/explorer/failedTestHandler.ts index 090ef95945db..21973e564ed5 100644 --- a/src/client/testing/explorer/failedTestHandler.ts +++ b/src/client/testing/explorer/failedTestHandler.ts @@ -11,9 +11,9 @@ import { Commands } from '../../common/constants'; import '../../common/extensions'; import { IDisposable, IDisposableRegistry } from '../../common/types'; import { debounceAsync } from '../../common/utils/decorators'; -import { getTestType } from '../common/testUtils'; -import { ITestCollectionStorageService, TestStatus, TestType } from '../common/types'; -import { TestDataItem } from '../types'; +import { getTestDataItemType } from '../common/testUtils'; +import { ITestCollectionStorageService, TestStatus } from '../common/types'; +import { TestDataItem, TestDataItemType } from '../types'; @injectable() export class FailedTestHandler implements IExtensionSingleActivationService, IDisposable { @@ -32,7 +32,7 @@ export class FailedTestHandler implements IExtensionSingleActivationService, IDi } public onDidChangeTestData(args: { uri: Uri; data?: TestDataItem }): void { if (args.data && (args.data.status === TestStatus.Error || args.data.status === TestStatus.Fail) && - getTestType(args.data) === TestType.testFunction) { + getTestDataItemType(args.data) === TestDataItemType.function) { this.failedItems.push(args.data); this.revealFailedNodes().ignoreErrors(); } diff --git a/src/client/testing/explorer/testTreeViewItem.ts b/src/client/testing/explorer/testTreeViewItem.ts index dd4d614a47e5..0b34e3eeb07b 100644 --- a/src/client/testing/explorer/testTreeViewItem.ts +++ b/src/client/testing/explorer/testTreeViewItem.ts @@ -10,12 +10,12 @@ import { Commands } from '../../common/constants'; import { getIcon } from '../../common/utils/icons'; import { noop } from '../../common/utils/misc'; import { Icons } from '../common/constants'; -import { getTestType, isSubtestsParent } from '../common/testUtils'; -import { TestResult, TestStatus, TestSuite, TestType } from '../common/types'; -import { TestDataItem } from '../types'; +import { getTestDataItemType, isSubtestsParent } from '../common/testUtils'; +import { TestResult, TestStatus, TestSuite } from '../common/types'; +import { TestDataItem, TestDataItemType } from '../types'; function getDefaultCollapsibleState(data: TestDataItem): TreeItemCollapsibleState { - return getTestType(data) === TestType.testFunction ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed; + return getTestDataItemType(data) === TestDataItemType.function ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed; } /** @@ -24,7 +24,7 @@ function getDefaultCollapsibleState(data: TestDataItem): TreeItemCollapsibleStat * TestDataItem. */ export class TestTreeItem extends TreeItem { - public readonly testType: TestType; + public readonly testType: TestDataItemType; constructor( public readonly resource: Uri, @@ -32,7 +32,7 @@ export class TestTreeItem extends TreeItem { collapsibleStatue: TreeItemCollapsibleState = getDefaultCollapsibleState(data) ) { super(data.name, collapsibleStatue); - this.testType = getTestType(this.data); + this.testType = getTestDataItemType(this.data); this.setCommand(); } public get contextValue(): string { @@ -40,7 +40,7 @@ export class TestTreeItem extends TreeItem { } public get iconPath(): string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon { - if (this.testType === TestType.testWorkspaceFolder) { + if (this.testType === TestDataItemType.workspaceFolder) { return ThemeIcon.Folder; } if (!this.data) { @@ -70,14 +70,14 @@ export class TestTreeItem extends TreeItem { } public get tooltip(): string { - if (!this.data || this.testType === TestType.testWorkspaceFolder) { + if (!this.data || this.testType === TestDataItemType.workspaceFolder) { return ''; } const result = this.data as TestResult; if (!result.status || result.status === TestStatus.Idle || result.status === TestStatus.Unknown || result.status === TestStatus.Skipped) { return ''; } - if (this.testType !== TestType.testFunction) { + if (this.testType !== TestDataItemType.function) { if (result.functionsPassed === undefined) { return ''; } @@ -113,15 +113,15 @@ export class TestTreeItem extends TreeItem { private setCommand() { switch (this.testType) { - case TestType.testFile: { + case TestDataItemType.file: { this.command = { command: Commands.navigateToTestFile, title: 'Open', arguments: [this.resource, this.data] }; break; } - case TestType.testFunction: { + case TestDataItemType.function: { this.command = { command: Commands.navigateToTestFunction, title: 'Open', arguments: [this.resource, this.data, false] }; break; } - case TestType.testSuite: { + case TestDataItemType.suite: { if (isSubtestsParent(this.data as TestSuite)) { this.command = { command: Commands.navigateToTestFunction, title: 'Open', arguments: [this.resource, this.data, false] }; break; diff --git a/src/client/testing/explorer/testTreeViewProvider.ts b/src/client/testing/explorer/testTreeViewProvider.ts index d9735086c6ec..54aaf7abd9a4 100644 --- a/src/client/testing/explorer/testTreeViewProvider.ts +++ b/src/client/testing/explorer/testTreeViewProvider.ts @@ -11,9 +11,12 @@ import { IDisposable, IDisposableRegistry } from '../../common/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { CommandSource } from '../common/constants'; -import { getChildren, getParent, getTestType } from '../common/testUtils'; -import { ITestCollectionStorageService, Tests, TestStatus, TestType } from '../common/types'; -import { ITestDataItemResource, ITestManagementService, ITestTreeViewProvider, TestDataItem, TestWorkspaceFolder, WorkspaceTestStatus } from '../types'; +import { getChildren, getParent, getTestDataItemType } from '../common/testUtils'; +import { ITestCollectionStorageService, Tests, TestStatus } from '../common/types'; +import { + ITestDataItemResource, ITestManagementService, ITestTreeViewProvider, + TestDataItem, TestDataItemType, TestWorkspaceFolder, WorkspaceTestStatus +} from '../types'; import { TestTreeItem } from './testTreeViewItem'; @injectable() @@ -182,7 +185,7 @@ export class TestTreeViewProvider implements ITestTreeViewProvider, ITestDataIte private async shouldElementBeExpandedByDefault(element: TestDataItem) { const parent = await this.getParent(element); - if (!parent || getTestType(parent) === TestType.testWorkspaceFolder) { + if (!parent || getTestDataItemType(parent) === TestDataItemType.workspaceFolder) { return true; } return false; diff --git a/src/client/testing/types.ts b/src/client/testing/types.ts index 46e2e62de579..b10999f1f175 100644 --- a/src/client/testing/types.ts +++ b/src/client/testing/types.ts @@ -157,6 +157,13 @@ export interface ILocationStackFrameDetails { export type WorkspaceTestStatus = { workspace: Uri; status: TestStatus }; +export enum TestDataItemType { + workspaceFolder = 'testWorkspaceFolder', + folder = 'testFolder', + file = 'testFile', + suite = 'testSuite', + function = 'testFunction' +} export type TestDataItem = TestWorkspaceFolder | TestFolder | TestFile | TestSuite | TestFunction; export class TestWorkspaceFolder { diff --git a/src/test/testing/common/services/storageService.unit.test.ts b/src/test/testing/common/services/storageService.unit.test.ts index 46869040520a..fa112024d810 100644 --- a/src/test/testing/common/services/storageService.unit.test.ts +++ b/src/test/testing/common/services/storageService.unit.test.ts @@ -5,7 +5,11 @@ import * as assert from 'assert'; import { copyDesiredTestResults } from '../../../../client/testing/common/testUtils'; -import { FlattenedTestFunction, FlattenedTestSuite, TestFile, TestFolder, TestFunction, Tests, TestStatus, TestSuite, TestType } from '../../../../client/testing/common/types'; +import { + FlattenedTestFunction, FlattenedTestSuite, TestFile, TestFolder, + TestFunction, Tests, TestStatus, TestSuite +} from '../../../../client/testing/common/types'; +import { TestDataItemType } from '../../../../client/testing/types'; import { createMockTestDataItem } from '../testUtils.unit.test'; // tslint:disable:no-any max-func-body-length @@ -18,14 +22,14 @@ suite('Unit Tests - Storage Service', () => { }); function setupTestData1() { - const folder1 = createMockTestDataItem(TestType.testFolder, '1'); - const file1 = createMockTestDataItem(TestType.testFile, '1'); + const folder1 = createMockTestDataItem(TestDataItemType.folder, '1'); + const file1 = createMockTestDataItem(TestDataItemType.file, '1'); folder1.testFiles.push(file1); - const suite1 = createMockTestDataItem(TestType.testSuite, '1'); - const suite2 = createMockTestDataItem(TestType.testSuite, '2'); - const fn1 = createMockTestDataItem(TestType.testFunction, '1'); - const fn2 = createMockTestDataItem(TestType.testFunction, '2'); - const fn3 = createMockTestDataItem(TestType.testFunction, '3'); + const suite1 = createMockTestDataItem(TestDataItemType.suite, '1'); + const suite2 = createMockTestDataItem(TestDataItemType.suite, '2'); + const fn1 = createMockTestDataItem(TestDataItemType.function, '1'); + const fn2 = createMockTestDataItem(TestDataItemType.function, '2'); + const fn3 = createMockTestDataItem(TestDataItemType.function, '3'); file1.suites.push(suite1); file1.suites.push(suite2); file1.functions.push(fn1); @@ -62,14 +66,14 @@ suite('Unit Tests - Storage Service', () => { } function setupTestData2() { - const folder1 = createMockTestDataItem(TestType.testFolder, '1'); - const file1 = createMockTestDataItem(TestType.testFile, '1'); + const folder1 = createMockTestDataItem(TestDataItemType.folder, '1'); + const file1 = createMockTestDataItem(TestDataItemType.file, '1'); folder1.testFiles.push(file1); - const suite1 = createMockTestDataItem(TestType.testSuite, '1'); - const suite2 = createMockTestDataItem(TestType.testSuite, '2'); - const fn1 = createMockTestDataItem(TestType.testFunction, '1'); - const fn2 = createMockTestDataItem(TestType.testFunction, '2'); - const fn3 = createMockTestDataItem(TestType.testFunction, '3'); + const suite1 = createMockTestDataItem(TestDataItemType.suite, '1'); + const suite2 = createMockTestDataItem(TestDataItemType.suite, '2'); + const fn1 = createMockTestDataItem(TestDataItemType.function, '1'); + const fn2 = createMockTestDataItem(TestDataItemType.function, '2'); + const fn3 = createMockTestDataItem(TestDataItemType.function, '3'); file1.suites.push(suite1); file1.suites.push(suite2); suite1.functions.push(fn1); diff --git a/src/test/testing/common/services/testResultsService.unit.test.ts b/src/test/testing/common/services/testResultsService.unit.test.ts index 48a73ff09e7e..b12673daff7a 100644 --- a/src/test/testing/common/services/testResultsService.unit.test.ts +++ b/src/test/testing/common/services/testResultsService.unit.test.ts @@ -6,7 +6,11 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { TestResultsService } from '../../../../client/testing/common/services/testResultsService'; -import { FlattenedTestFunction, FlattenedTestSuite, ITestVisitor, TestFile, TestFolder, TestFunction, Tests, TestStatus, TestSuite, TestType } from '../../../../client/testing/common/types'; +import { + FlattenedTestFunction, FlattenedTestSuite, ITestVisitor, TestFile, + TestFolder, TestFunction, Tests, TestStatus, TestSuite +} from '../../../../client/testing/common/types'; +import { TestDataItemType } from '../../../../client/testing/types'; import { createMockTestDataItem } from '../testUtils.unit.test'; // tslint:disable:no-any max-func-body-length @@ -19,53 +23,53 @@ suite('Unit Tests - Tests Results Service', () => { let file1: TestFile, file2: TestFile, file3: TestFile, file4: TestFile, file5: TestFile; setup(() => { resultResetVisitor = typemoq.Mock.ofType(); - folder1 = createMockTestDataItem(TestType.testFolder); - folder2 = createMockTestDataItem(TestType.testFolder); - folder3 = createMockTestDataItem(TestType.testFolder); - folder4 = createMockTestDataItem(TestType.testFolder); - folder5 = createMockTestDataItem(TestType.testFolder); + folder1 = createMockTestDataItem(TestDataItemType.folder); + folder2 = createMockTestDataItem(TestDataItemType.folder); + folder3 = createMockTestDataItem(TestDataItemType.folder); + folder4 = createMockTestDataItem(TestDataItemType.folder); + folder5 = createMockTestDataItem(TestDataItemType.folder); folder1.folders.push(folder2); folder1.folders.push(folder3); folder2.folders.push(folder4); folder3.folders.push(folder5); - file1 = createMockTestDataItem(TestType.testFile); - file2 = createMockTestDataItem(TestType.testFile); - file3 = createMockTestDataItem(TestType.testFile); - file4 = createMockTestDataItem(TestType.testFile); - file5 = createMockTestDataItem(TestType.testFile); + file1 = createMockTestDataItem(TestDataItemType.file); + file2 = createMockTestDataItem(TestDataItemType.file); + file3 = createMockTestDataItem(TestDataItemType.file); + file4 = createMockTestDataItem(TestDataItemType.file); + file5 = createMockTestDataItem(TestDataItemType.file); folder1.testFiles.push(file1); folder3.testFiles.push(file2); folder3.testFiles.push(file3); folder4.testFiles.push(file5); folder5.testFiles.push(file4); - suite1 = createMockTestDataItem(TestType.testSuite); - suite2 = createMockTestDataItem(TestType.testSuite); - suite3 = createMockTestDataItem(TestType.testSuite); - suite4 = createMockTestDataItem(TestType.testSuite); - suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); + suite1 = createMockTestDataItem(TestDataItemType.suite); + suite2 = createMockTestDataItem(TestDataItemType.suite); + suite3 = createMockTestDataItem(TestDataItemType.suite); + suite4 = createMockTestDataItem(TestDataItemType.suite); + suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); fn1.passed = true; - const fn2 = createMockTestDataItem(TestType.testFunction); + const fn2 = createMockTestDataItem(TestDataItemType.function); fn2.passed = undefined; - const fn3 = createMockTestDataItem(TestType.testFunction); + const fn3 = createMockTestDataItem(TestDataItemType.function); fn3.passed = true; - const fn4 = createMockTestDataItem(TestType.testFunction); + const fn4 = createMockTestDataItem(TestDataItemType.function); fn4.passed = false; - const fn5 = createMockTestDataItem(TestType.testFunction); + const fn5 = createMockTestDataItem(TestDataItemType.function); fn5.passed = undefined; - const fn6 = createMockTestDataItem(TestType.testFunction); + const fn6 = createMockTestDataItem(TestDataItemType.function); fn6.passed = true; - const fn7 = createMockTestDataItem(TestType.testFunction); + const fn7 = createMockTestDataItem(TestDataItemType.function); fn7.passed = undefined; - const fn8 = createMockTestDataItem(TestType.testFunction); + const fn8 = createMockTestDataItem(TestDataItemType.function); fn8.passed = false; - const fn9 = createMockTestDataItem(TestType.testFunction); + const fn9 = createMockTestDataItem(TestDataItemType.function); fn9.passed = true; - const fn10 = createMockTestDataItem(TestType.testFunction); + const fn10 = createMockTestDataItem(TestDataItemType.function); fn10.passed = true; - const fn11 = createMockTestDataItem(TestType.testFunction); + const fn11 = createMockTestDataItem(TestDataItemType.function); fn11.passed = true; file1.suites.push(suite1); file1.suites.push(suite2); diff --git a/src/test/testing/common/services/testStatusService.unit.test.ts b/src/test/testing/common/services/testStatusService.unit.test.ts index 43f5e9880d7e..83d88a7a0a47 100644 --- a/src/test/testing/common/services/testStatusService.unit.test.ts +++ b/src/test/testing/common/services/testStatusService.unit.test.ts @@ -9,8 +9,12 @@ import { Uri } from 'vscode'; import { TestCollectionStorageService } from '../../../../client/testing/common/services/storageService'; import { TestsStatusUpdaterService } from '../../../../client/testing/common/services/testsStatusService'; import { visitRecursive } from '../../../../client/testing/common/testVisitors/visitor'; -import { FlattenedTestFunction, FlattenedTestSuite, ITestCollectionStorageService, ITestsStatusUpdaterService, TestFile, TestFolder, TestFunction, Tests, TestStatus, TestSuite, TestType } from '../../../../client/testing/common/types'; -import { TestDataItem } from '../../../../client/testing/types'; +import { + FlattenedTestFunction, FlattenedTestSuite, ITestCollectionStorageService, + ITestsStatusUpdaterService, TestFile, TestFolder, TestFunction, Tests, + TestStatus, TestSuite +} from '../../../../client/testing/common/types'; +import { TestDataItem, TestDataItemType } from '../../../../client/testing/types'; import { createMockTestDataItem } from '../testUtils.unit.test'; // tslint:disable:no-any max-func-body-length @@ -22,35 +26,35 @@ suite('Unit Tests - Tests Status Updater', () => { setup(() => { storage = mock(TestCollectionStorageService); updater = new TestsStatusUpdaterService(instance(storage)); - const folder1 = createMockTestDataItem(TestType.testFolder); - const folder2 = createMockTestDataItem(TestType.testFolder); - const folder3 = createMockTestDataItem(TestType.testFolder); - const folder4 = createMockTestDataItem(TestType.testFolder); - const folder5 = createMockTestDataItem(TestType.testFolder); + const folder1 = createMockTestDataItem(TestDataItemType.folder); + const folder2 = createMockTestDataItem(TestDataItemType.folder); + const folder3 = createMockTestDataItem(TestDataItemType.folder); + const folder4 = createMockTestDataItem(TestDataItemType.folder); + const folder5 = createMockTestDataItem(TestDataItemType.folder); folder1.folders.push(folder2); folder1.folders.push(folder3); folder2.folders.push(folder4); folder3.folders.push(folder5); - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); folder1.testFiles.push(file1); folder3.testFiles.push(file2); folder3.testFiles.push(file3); folder5.testFiles.push(file4); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); - const fn2 = createMockTestDataItem(TestType.testFunction); - const fn3 = createMockTestDataItem(TestType.testFunction); - const fn4 = createMockTestDataItem(TestType.testFunction); - const fn5 = createMockTestDataItem(TestType.testFunction); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); + const fn2 = createMockTestDataItem(TestDataItemType.function); + const fn3 = createMockTestDataItem(TestDataItemType.function); + const fn4 = createMockTestDataItem(TestDataItemType.function); + const fn5 = createMockTestDataItem(TestDataItemType.function); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); diff --git a/src/test/testing/common/testUtils.unit.test.ts b/src/test/testing/common/testUtils.unit.test.ts index 2d907c796419..24261379fae3 100644 --- a/src/test/testing/common/testUtils.unit.test.ts +++ b/src/test/testing/common/testUtils.unit.test.ts @@ -8,15 +8,15 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { getNamesAndValues } from '../../../client/common/utils/enum'; import { - getChildren, getParent, getParentFile, getParentSuite, getTestFile, - getTestFolder, getTestFunction, getTestSuite, getTestType + getChildren, getParent, getParentFile, getParentSuite, getTestDataItemType, + getTestFile, getTestFolder, getTestFunction, getTestSuite } from '../../../client/testing/common/testUtils'; import { FlattenedTestFunction, FlattenedTestSuite, SubtestParent, TestFile, - TestFolder, TestFunction, Tests, TestSuite, TestType + TestFolder, TestFunction, Tests, TestSuite } from '../../../client/testing/common/types'; import { - TestDataItem, TestWorkspaceFolder + TestDataItem, TestDataItemType, TestWorkspaceFolder } from '../../../client/testing/types'; // tslint:disable:prefer-template @@ -36,7 +36,7 @@ function longestCommonSubstring(strings: string[]): string { } export function createMockTestDataItem( - type: TestType, + type: TestDataItemType, nameSuffix: string = '', name?: string, nameToRun?: string @@ -78,15 +78,15 @@ export function createMockTestDataItem( }; switch (type) { - case TestType.testFile: + case TestDataItemType.file: return file as T; - case TestType.testFolder: + case TestDataItemType.folder: return folder as T; - case TestType.testFunction: + case TestDataItemType.function: return func as T; - case TestType.testSuite: + case TestDataItemType.suite: return suite as T; - case TestType.testWorkspaceFolder: + case TestDataItemType.workspaceFolder: return (new TestWorkspaceFolder({ uri: Uri.file(''), name: 'a', index: 0 })) as T; default: throw new Error('Unknown type'); @@ -147,24 +147,24 @@ export function createTests( // tslint:disable:max-func-body-length no-any suite('Unit Tests - TestUtils', () => { - test('Get TestType for Folders', () => { - const item = createMockTestDataItem(TestType.testFolder); - assert.equal(getTestType(item), TestType.testFolder); + test('Get TestDataItemType for Folders', () => { + const item = createMockTestDataItem(TestDataItemType.folder); + assert.equal(getTestDataItemType(item), TestDataItemType.folder); }); - test('Get TestType for Files', () => { - const item = createMockTestDataItem(TestType.testFile); - assert.equal(getTestType(item), TestType.testFile); + test('Get TestDataItemType for Files', () => { + const item = createMockTestDataItem(TestDataItemType.file); + assert.equal(getTestDataItemType(item), TestDataItemType.file); }); - test('Get TestType for Functions', () => { - const item = createMockTestDataItem(TestType.testFunction); - assert.equal(getTestType(item), TestType.testFunction); + test('Get TestDataItemType for Functions', () => { + const item = createMockTestDataItem(TestDataItemType.function); + assert.equal(getTestDataItemType(item), TestDataItemType.function); }); - test('Get TestType for Suites', () => { - const item = createMockTestDataItem(TestType.testSuite); - assert.equal(getTestType(item), TestType.testSuite); + test('Get TestDataItemType for Suites', () => { + const item = createMockTestDataItem(TestDataItemType.suite); + assert.equal(getTestDataItemType(item), TestDataItemType.suite); }); test('Casting to a specific items', () => { - for (const typeName of getNamesAndValues(TestType)) { + for (const typeName of getNamesAndValues(TestDataItemType)) { const item = createMockTestDataItem(typeName.value); const file = getTestFile(item); const folder = getTestFolder(item); @@ -172,7 +172,7 @@ suite('Unit Tests - TestUtils', () => { const func = getTestFunction(item); switch (typeName.value) { - case TestType.testFile: + case TestDataItemType.file: { assert.equal(file, item); assert.equal(folder, undefined); @@ -180,7 +180,7 @@ suite('Unit Tests - TestUtils', () => { assert.equal(func, undefined); break; } - case TestType.testFolder: + case TestDataItemType.folder: { assert.equal(file, undefined); assert.equal(folder, item); @@ -188,7 +188,7 @@ suite('Unit Tests - TestUtils', () => { assert.equal(func, undefined); break; } - case TestType.testFunction: + case TestDataItemType.function: { assert.equal(file, undefined); assert.equal(folder, undefined); @@ -196,7 +196,7 @@ suite('Unit Tests - TestUtils', () => { assert.equal(func, item); break; } - case TestType.testSuite: + case TestDataItemType.suite: { assert.equal(file, undefined); assert.equal(folder, undefined); @@ -204,7 +204,7 @@ suite('Unit Tests - TestUtils', () => { assert.equal(func, undefined); break; } - case TestType.testWorkspaceFolder: + case TestDataItemType.workspaceFolder: { assert.equal(file, undefined); assert.equal(folder, undefined); @@ -218,11 +218,11 @@ suite('Unit Tests - TestUtils', () => { } }); test('Get Parent of folder', () => { - const folder1 = createMockTestDataItem(TestType.testFolder); - const folder2 = createMockTestDataItem(TestType.testFolder); - const folder3 = createMockTestDataItem(TestType.testFolder); - const folder4 = createMockTestDataItem(TestType.testFolder); - const folder5 = createMockTestDataItem(TestType.testFolder); + const folder1 = createMockTestDataItem(TestDataItemType.folder); + const folder2 = createMockTestDataItem(TestDataItemType.folder); + const folder3 = createMockTestDataItem(TestDataItemType.folder); + const folder4 = createMockTestDataItem(TestDataItemType.folder); + const folder5 = createMockTestDataItem(TestDataItemType.folder); folder1.folders.push(folder2); folder1.folders.push(folder3); folder2.folders.push(folder4); @@ -242,20 +242,20 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParent(tests, folder5), folder3); }); test('Get Parent of file', () => { - const folder1 = createMockTestDataItem(TestType.testFolder); - const folder2 = createMockTestDataItem(TestType.testFolder); - const folder3 = createMockTestDataItem(TestType.testFolder); - const folder4 = createMockTestDataItem(TestType.testFolder); - const folder5 = createMockTestDataItem(TestType.testFolder); + const folder1 = createMockTestDataItem(TestDataItemType.folder); + const folder2 = createMockTestDataItem(TestDataItemType.folder); + const folder3 = createMockTestDataItem(TestDataItemType.folder); + const folder4 = createMockTestDataItem(TestDataItemType.folder); + const folder5 = createMockTestDataItem(TestDataItemType.folder); folder1.folders.push(folder2); folder1.folders.push(folder3); folder2.folders.push(folder4); folder3.folders.push(folder5); - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); folder1.testFiles.push(file1); folder3.testFiles.push(file2); folder3.testFiles.push(file3); @@ -274,20 +274,20 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParent(tests, file4), folder5); }); test('Get Parent File', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); - const fn2 = createMockTestDataItem(TestType.testFunction); - const fn3 = createMockTestDataItem(TestType.testFunction); - const fn4 = createMockTestDataItem(TestType.testFunction); - const fn5 = createMockTestDataItem(TestType.testFunction); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); + const fn2 = createMockTestDataItem(TestDataItemType.function); + const fn3 = createMockTestDataItem(TestDataItemType.function); + const fn4 = createMockTestDataItem(TestDataItemType.function); + const fn5 = createMockTestDataItem(TestDataItemType.function); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); @@ -361,20 +361,20 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParentFile(tests, suite5), file3); }); test('Get Parent Suite', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); - const fn2 = createMockTestDataItem(TestType.testFunction); - const fn3 = createMockTestDataItem(TestType.testFunction); - const fn4 = createMockTestDataItem(TestType.testFunction); - const fn5 = createMockTestDataItem(TestType.testFunction); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); + const fn2 = createMockTestDataItem(TestDataItemType.function); + const fn3 = createMockTestDataItem(TestDataItemType.function); + const fn4 = createMockTestDataItem(TestDataItemType.function); + const fn5 = createMockTestDataItem(TestDataItemType.function); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); @@ -448,9 +448,9 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParentSuite(tests, suite5), suite4); }); test('Get Parent file throws an exception', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); + const file1 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); const flattendSuite1: FlattenedTestSuite = { testSuite: suite1, xmlClassName: suite1.xmlName @@ -471,9 +471,9 @@ suite('Unit Tests - TestUtils', () => { assert.throws(() => getParentFile(tests, suite1), new RegExp('No parent file for provided test item')); }); test('Get parent of orphaned items', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); + const file1 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); const flattendSuite1: FlattenedTestSuite = { testSuite: suite1, xmlClassName: suite1.xmlName @@ -494,15 +494,15 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParent(tests, suite1), undefined); }); test('Get Parent of suite', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); @@ -543,20 +543,20 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParent(tests, suite5), suite4); }); test('Get Parent of function', () => { - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); - const fn2 = createMockTestDataItem(TestType.testFunction); - const fn3 = createMockTestDataItem(TestType.testFunction); - const fn4 = createMockTestDataItem(TestType.testFunction); - const fn5 = createMockTestDataItem(TestType.testFunction); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); + const fn2 = createMockTestDataItem(TestDataItemType.function); + const fn3 = createMockTestDataItem(TestDataItemType.function); + const fn4 = createMockTestDataItem(TestDataItemType.function); + const fn5 = createMockTestDataItem(TestDataItemType.function); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); @@ -622,16 +622,16 @@ suite('Unit Tests - TestUtils', () => { assert.equal(getParent(tests, fn5), suite3); }); test('Get parent of parameterized function', () => { - const folder = createMockTestDataItem(TestType.testFolder); - const file = createMockTestDataItem(TestType.testFile); - const func1 = createMockTestDataItem(TestType.testFunction); - const func2 = createMockTestDataItem(TestType.testFunction); - const func3 = createMockTestDataItem(TestType.testFunction); + const folder = createMockTestDataItem(TestDataItemType.folder); + const file = createMockTestDataItem(TestDataItemType.file); + const func1 = createMockTestDataItem(TestDataItemType.function); + const func2 = createMockTestDataItem(TestDataItemType.function); + const func3 = createMockTestDataItem(TestDataItemType.function); const subParent1 = createSubtestParent([func2, func3]); - const suite = createMockTestDataItem(TestType.testSuite); - const func4 = createMockTestDataItem(TestType.testFunction); - const func5 = createMockTestDataItem(TestType.testFunction); - const func6 = createMockTestDataItem(TestType.testFunction); + const suite = createMockTestDataItem(TestDataItemType.suite); + const func4 = createMockTestDataItem(TestDataItemType.function); + const func5 = createMockTestDataItem(TestDataItemType.function); + const func6 = createMockTestDataItem(TestDataItemType.function); const subParent2 = createSubtestParent([func5, func6]); folder.testFiles.push(file); file.functions.push(func1); @@ -662,16 +662,16 @@ suite('Unit Tests - TestUtils', () => { }); test('Get children of parameterized function', () => { const filename = path.join('tests', 'test_spam.py'); - const folder = createMockTestDataItem(TestType.testFolder, 'tests'); - const file = createMockTestDataItem(TestType.testFile, filename); - const func1 = createMockTestDataItem(TestType.testFunction, 'test_x'); - const func2 = createMockTestDataItem(TestType.testFunction, 'test_y'); - const func3 = createMockTestDataItem(TestType.testFunction, 'test_z'); + const folder = createMockTestDataItem(TestDataItemType.folder, 'tests'); + const file = createMockTestDataItem(TestDataItemType.file, filename); + const func1 = createMockTestDataItem(TestDataItemType.function, 'test_x'); + const func2 = createMockTestDataItem(TestDataItemType.function, 'test_y'); + const func3 = createMockTestDataItem(TestDataItemType.function, 'test_z'); const subParent1 = createSubtestParent([func2, func3]); - const suite = createMockTestDataItem(TestType.testSuite); - const func4 = createMockTestDataItem(TestType.testFunction); - const func5 = createMockTestDataItem(TestType.testFunction); - const func6 = createMockTestDataItem(TestType.testFunction); + const suite = createMockTestDataItem(TestDataItemType.suite); + const func4 = createMockTestDataItem(TestDataItemType.function); + const func5 = createMockTestDataItem(TestDataItemType.function); + const func6 = createMockTestDataItem(TestDataItemType.function); const subParent2 = createSubtestParent([func5, func6]); folder.testFiles.push(file); file.functions.push(func1); diff --git a/src/test/testing/explorer/testTreeViewItem.unit.test.ts b/src/test/testing/explorer/testTreeViewItem.unit.test.ts index 569abf9e1f1b..df6a89cca393 100644 --- a/src/test/testing/explorer/testTreeViewItem.unit.test.ts +++ b/src/test/testing/explorer/testTreeViewItem.unit.test.ts @@ -9,12 +9,10 @@ import { Commands } from '../../../client/common/constants'; import { - TestFile, TestFolder, - TestFunction, TestSuite, TestType + TestFile, TestFolder, TestFunction, TestSuite } from '../../../client/testing/common/types'; -import { - TestTreeItem -} from '../../../client/testing/explorer/testTreeViewItem'; +import { TestTreeItem } from '../../../client/testing/explorer/testTreeViewItem'; +import { TestDataItemType } from '../../../client/testing/types'; import { createMockTestDataItem, createSubtestParent } from '../common/testUtils.unit.test'; @@ -58,8 +56,8 @@ suite('Unit Tests Test Explorer View Items', () => { test('Test subtest parent created into test view item', () => { const subtestParent = createSubtestParent([ - createMockTestDataItem(TestType.testFunction, 'test_x'), - createMockTestDataItem(TestType.testFunction, 'test_y') + createMockTestDataItem(TestDataItemType.function, 'test_x'), + createMockTestDataItem(TestDataItemType.function, 'test_y') ]); const viewItem = new TestTreeItem(resource, subtestParent.asSuite); diff --git a/src/test/testing/explorer/testTreeViewProvider.unit.test.ts b/src/test/testing/explorer/testTreeViewProvider.unit.test.ts index b797186c06d5..91b5e9887041 100644 --- a/src/test/testing/explorer/testTreeViewProvider.unit.test.ts +++ b/src/test/testing/explorer/testTreeViewProvider.unit.test.ts @@ -14,14 +14,14 @@ import { Commands } from '../../../client/common/constants'; import { IDisposable } from '../../../client/common/types'; import { CommandSource } from '../../../client/testing/common/constants'; import { TestCollectionStorageService } from '../../../client/testing/common/services/storageService'; -import { getTestType } from '../../../client/testing/common/testUtils'; +import { getTestDataItemType } from '../../../client/testing/common/testUtils'; import { - ITestCollectionStorageService, TestFile, TestFolder, Tests, TestStatus, TestType + ITestCollectionStorageService, TestFile, TestFolder, Tests, TestStatus } from '../../../client/testing/common/types'; import { TestTreeItem } from '../../../client/testing/explorer/testTreeViewItem'; import { TestTreeViewProvider } from '../../../client/testing/explorer/testTreeViewProvider'; import { UnitTestManagementService } from '../../../client/testing/main'; -import { TestDataItem, TestWorkspaceFolder } from '../../../client/testing/types'; +import { TestDataItem, TestDataItemType, TestWorkspaceFolder } from '../../../client/testing/types'; import { noop } from '../../core'; import { createMockTestExplorer as createMockTestTreeProvider, createMockTestsData, @@ -296,22 +296,22 @@ suite('Unit Tests Test Explorer TestTreeViewProvider', () => { let parent = (await testTreeProvider.getParent(testFunction))!; expect(parent.name).to.be.equal(testSuite.name, 'Function within a test suite not returning the suite as parent.'); - let parentType = getTestType(parent); - expect(parentType).to.be.equal(TestType.testSuite); + let parentType = getTestDataItemType(parent); + expect(parentType).to.be.equal(TestDataItemType.suite); parent = (await testTreeProvider.getParent(testSuite))!; expect(parent.name).to.be.equal(testFile.name, 'Suite within a test file not returning the test file as parent.'); - parentType = getTestType(parent); - expect(parentType).to.be.equal(TestType.testFile); + parentType = getTestDataItemType(parent); + expect(parentType).to.be.equal(TestDataItemType.file); parent = (await testTreeProvider.getParent(outerTestFunction))!; expect(parent.name).to.be.equal(testFile.name, 'Function within a test file not returning the test file as parent.'); - parentType = getTestType(parent); - expect(parentType).to.be.equal(TestType.testFile); + parentType = getTestDataItemType(parent); + expect(parentType).to.be.equal(TestDataItemType.file); parent = (await testTreeProvider.getParent(testFile))!; - parentType = getTestType(parent!); - expect(parentType).to.be.equal(TestType.testFolder); + parentType = getTestDataItemType(parent!); + expect(parentType).to.be.equal(TestDataItemType.folder); }); test('Get children is working for each item type', async () => { @@ -334,7 +334,7 @@ suite('Unit Tests Test Explorer TestTreeViewProvider', () => { expect(children.length).to.be.equal(1, 'Suite a single function should only return one child.'); children.forEach((child: TestDataItem) => { expect(child.name).oneOf(['test_fn']); - expect(getTestType(child)).to.be.equal(TestType.testFunction); + expect(getTestDataItemType(child)).to.be.equal(TestDataItemType.function); }); children = await testTreeProvider.getChildren(outerTestFunction); diff --git a/src/test/testing/results.ts b/src/test/testing/results.ts index fd809293e1b6..3bbcfeb6225f 100644 --- a/src/test/testing/results.ts +++ b/src/test/testing/results.ts @@ -69,7 +69,7 @@ export function findParentFile(parents: TestNode[]): TestFile | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; - if (parent.testType === TestType.testFile) { + if (parent.testType === TestType.file) { return parent as TestFile; } } @@ -81,7 +81,7 @@ export function findParentSuite(parents: TestNode[]): TestSuite | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; - if (parent.testType === TestType.testSuite) { + if (parent.testType === TestType.suite) { return parent as TestSuite; } } @@ -147,7 +147,7 @@ export namespace nodes { nameToRun: nameToRun || dirname, folders: [], testFiles: [], - testType: TestType.testFolder, + testType: TestType.folder, // result time: 0, status: TestStatus.Unknown @@ -175,7 +175,7 @@ export namespace nodes { xmlName: xmlName!, suites: [], functions: [], - testType: TestType.testFile, + testType: TestType.file, // result time: 0, status: TestStatus.Unknown @@ -199,7 +199,7 @@ export namespace nodes { isInstance: isInstance, suites: [], functions: [], - testType: TestType.testSuite, + testType: TestType.suite, // result time: 0, status: TestStatus.Unknown @@ -217,7 +217,7 @@ export namespace nodes { name: name, nameToRun: nameToRun || name, subtestParent: subtestParent, - testType: TestType.testFunction, + testType: TestType.function, // result time: 0, status: TestStatus.Unknown @@ -390,18 +390,18 @@ namespace declarative { resource ); switch (parsed.testType) { - case TestType.testFolder: + case TestType.folder: tests.testFolders.push(node as TestFolder); break; - case TestType.testFile: + case TestType.file: tests.testFiles.push(node as TestFile); break; - case TestType.testSuite: + case TestType.suite: tests.testSuites.push( flattenSuite(node as TestSuite, parents) ); break; - case TestType.testFunction: + case TestType.function: // This does not deal with subtests? tests.testFunctions.push( flattenFunction(node as TestFunction, parents) @@ -441,7 +441,7 @@ namespace declarative { let testType: TestType; if (name.endsWith('/')) { // folder - testType = TestType.testFolder; + testType = TestType.folder; while (name.endsWith('/')) { name = name.slice(0, -1); } @@ -450,24 +450,24 @@ namespace declarative { if (name.includes('/')) { throw Error('filename must not include directories'); } - testType = TestType.testFile; + testType = TestType.file; } else if (name.startsWith('<')) { // suite if (!name.endsWith('>')) { throw Error('suite missing closing bracket'); } - testType = TestType.testSuite; + testType = TestType.suite; name = name.slice(1, -1); } else { // test - testType = TestType.testFunction; + testType = TestType.function; } // Parse the results. const result: TestResult = { time: 0 }; - if (parts.length !== 0 && testType !== TestType.testFunction) { + if (parts.length !== 0 && testType !== TestType.function) { throw Error('non-test nodes do not have results'); } switch (parts.length) { @@ -519,7 +519,7 @@ namespace declarative { parsed: ParsedTestNode ): boolean { if (parsed.indent === '') { - if (parsed.testType !== TestType.testFolder) { + if (parsed.testType !== TestType.folder) { throw Error('a top-level node must be a folder'); } return true; @@ -560,8 +560,8 @@ namespace declarative { resource?: Uri ): TestNode { switch (testType) { - case TestType.testFolder: - if (parent.testType !== TestType.testFolder) { + case TestType.folder: + if (parent.testType !== TestType.folder) { throw Error('parent must be a folder'); } return nodes.addDiscoveredSubFolder( @@ -570,8 +570,8 @@ namespace declarative { undefined, resource ); - case TestType.testFile: - if (parent.testType !== TestType.testFolder) { + case TestType.file: + if (parent.testType !== TestType.folder) { throw Error('parent must be a folder'); } return nodes.addDiscoveredFile( @@ -581,11 +581,11 @@ namespace declarative { undefined, resource ); - case TestType.testSuite: + case TestType.suite: let suiteParent: TestFile | TestSuite; - if (parent.testType === TestType.testFile) { + if (parent.testType === TestType.file) { suiteParent = parent as TestFile; - } else if (parent.testType === TestType.testSuite) { + } else if (parent.testType === TestType.suite) { suiteParent = parent as TestSuite; } else { throw Error('parent must be a file or suite'); @@ -599,13 +599,13 @@ namespace declarative { undefined, resource ); - case TestType.testFunction: + case TestType.function: let funcParent: TestFile | TestSuite; - if (parent.testType === TestType.testFile) { + if (parent.testType === TestType.file) { funcParent = parent as TestFile; - } else if (parent.testType === TestType.testSuite) { + } else if (parent.testType === TestType.suite) { funcParent = parent as TestSuite; - } else if (parent.testType === TestType.testFunction) { + } else if (parent.testType === TestType.function) { throw Error('not finished: use addDiscoveredSubTest()'); } else { throw Error('parent must be a file, suite, or function'); diff --git a/src/test/testing/unittest/unittest.unit.test.ts b/src/test/testing/unittest/unittest.unit.test.ts index 7cafcc6b493f..6a762d5453fa 100644 --- a/src/test/testing/unittest/unittest.unit.test.ts +++ b/src/test/testing/unittest/unittest.unit.test.ts @@ -19,8 +19,14 @@ import { TestResultsService } from '../../../client/testing/common/services/test import { TestsStatusUpdaterService } from '../../../client/testing/common/services/testsStatusService'; import { TestsHelper } from '../../../client/testing/common/testUtils'; import { TestResultResetVisitor } from '../../../client/testing/common/testVisitors/resultResetVisitor'; -import { FlattenedTestFunction, FlattenedTestSuite, ITestResultsService, ITestsHelper, ITestsStatusUpdaterService, TestFile, TestFolder, TestFunction, Tests, TestStatus, TestSuite, TestType } from '../../../client/testing/common/types'; -import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../../../client/testing/types'; +import { + FlattenedTestFunction, FlattenedTestSuite, ITestResultsService, + ITestsHelper, ITestsStatusUpdaterService, TestFile, TestFolder, + TestFunction, Tests, TestStatus, TestSuite +} from '../../../client/testing/common/types'; +import { + IArgumentsHelper, IArgumentsService, ITestManagerRunner, TestDataItemType +} from '../../../client/testing/types'; import { TestManager } from '../../../client/testing/unittest/main'; import { TestManagerRunner } from '../../../client/testing/unittest/runner'; import { ArgumentsService } from '../../../client/testing/unittest/services/argsService'; @@ -37,35 +43,35 @@ suite('Unit Tests - unittest - run failed tests', () => { let tests: Tests; function createTestData() { - const folder1 = createMockTestDataItem(TestType.testFolder); - const folder2 = createMockTestDataItem(TestType.testFolder); - const folder3 = createMockTestDataItem(TestType.testFolder); - const folder4 = createMockTestDataItem(TestType.testFolder); - const folder5 = createMockTestDataItem(TestType.testFolder); + const folder1 = createMockTestDataItem(TestDataItemType.folder); + const folder2 = createMockTestDataItem(TestDataItemType.folder); + const folder3 = createMockTestDataItem(TestDataItemType.folder); + const folder4 = createMockTestDataItem(TestDataItemType.folder); + const folder5 = createMockTestDataItem(TestDataItemType.folder); folder1.folders.push(folder2); folder1.folders.push(folder3); folder2.folders.push(folder4); folder3.folders.push(folder5); - const file1 = createMockTestDataItem(TestType.testFile); - const file2 = createMockTestDataItem(TestType.testFile); - const file3 = createMockTestDataItem(TestType.testFile); - const file4 = createMockTestDataItem(TestType.testFile); + const file1 = createMockTestDataItem(TestDataItemType.file); + const file2 = createMockTestDataItem(TestDataItemType.file); + const file3 = createMockTestDataItem(TestDataItemType.file); + const file4 = createMockTestDataItem(TestDataItemType.file); folder1.testFiles.push(file1); folder3.testFiles.push(file2); folder3.testFiles.push(file3); folder5.testFiles.push(file4); - const suite1 = createMockTestDataItem(TestType.testSuite); - const suite2 = createMockTestDataItem(TestType.testSuite); - const suite3 = createMockTestDataItem(TestType.testSuite); - const suite4 = createMockTestDataItem(TestType.testSuite); - const suite5 = createMockTestDataItem(TestType.testSuite); - const fn1 = createMockTestDataItem(TestType.testFunction); - const fn2 = createMockTestDataItem(TestType.testFunction); - const fn3 = createMockTestDataItem(TestType.testFunction); - const fn4 = createMockTestDataItem(TestType.testFunction); - const fn5 = createMockTestDataItem(TestType.testFunction); + const suite1 = createMockTestDataItem(TestDataItemType.suite); + const suite2 = createMockTestDataItem(TestDataItemType.suite); + const suite3 = createMockTestDataItem(TestDataItemType.suite); + const suite4 = createMockTestDataItem(TestDataItemType.suite); + const suite5 = createMockTestDataItem(TestDataItemType.suite); + const fn1 = createMockTestDataItem(TestDataItemType.function); + const fn2 = createMockTestDataItem(TestDataItemType.function); + const fn3 = createMockTestDataItem(TestDataItemType.function); + const fn4 = createMockTestDataItem(TestDataItemType.function); + const fn5 = createMockTestDataItem(TestDataItemType.function); file1.suites.push(suite1); file1.suites.push(suite2); file3.suites.push(suite3); From e1b7f84c1f1fa757535e69f6bd6abc217f971ae1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 18 Sep 2019 15:18:26 -0600 Subject: [PATCH 28/30] Factor out TestingNode and TestingXMLNode. --- src/client/testing/common/types.ts | 33 +++++++-------- src/test/testing/results.ts | 66 +++++++++++++++--------------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 440a385af05e..48d715416c39 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -68,7 +68,7 @@ export type TestsToRun = { //***************** // test results -export enum TestType { +export enum TestingType { folder = 'testFolder', file = 'testFile', suite = 'testSuite', @@ -99,39 +99,36 @@ export type TestResult = { functionsDidNotRun?: number; }; -export type TestFolder = TestResult & { - resource: Uri; +export type TestingNode = TestResult & { name: string; - testFiles: TestFile[]; nameToRun: string; + resource: Uri; +}; + +export type TestFolder = TestingNode & { folders: TestFolder[]; + testFiles: TestFile[]; }; -export type TestFile = TestResult & { - resource: Uri; - name: string; + +export type TestingXMLNode = TestingNode & { + xmlName: string; +}; + +export type TestFile = TestingXMLNode & { fullPath: string; functions: TestFunction[]; suites: TestSuite[]; - nameToRun: string; - xmlName: string; errorsWhenDiscovering?: string; }; -export type TestSuite = TestResult & { - resource: Uri; - name: string; +export type TestSuite = TestingXMLNode & { functions: TestFunction[]; suites: TestSuite[]; isUnitTest: Boolean; isInstance: Boolean; - nameToRun: string; - xmlName: string; }; -export type TestFunction = TestResult & { - resource: Uri; - name: string; - nameToRun: string; +export type TestFunction = TestingNode & { subtestParent?: SubtestParent; }; diff --git a/src/test/testing/results.ts b/src/test/testing/results.ts index 3bbcfeb6225f..136555c58758 100644 --- a/src/test/testing/results.ts +++ b/src/test/testing/results.ts @@ -7,8 +7,8 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { FlattenedTestFunction, FlattenedTestSuite, - SubtestParent, TestFile, TestFolder, TestFunction, TestProvider, - TestResult, Tests, TestStatus, TestSuite, TestSummary, TestType + SubtestParent, TestFile, TestFolder, TestFunction, TestingType, + TestProvider, TestResult, Tests, TestStatus, TestSuite, TestSummary } from '../../client/testing/common/types'; import { fixPath, getDedentedLines, getIndent, RESOURCE } from './helper'; @@ -19,7 +19,7 @@ type SuperTest = TestFunction & { export type TestItem = TestFolder | TestFile | TestSuite | SuperTest | TestFunction; export type TestNode = TestItem & { - testType: TestType; + testType: TestingType; }; // Return an initialized test results. @@ -69,7 +69,7 @@ export function findParentFile(parents: TestNode[]): TestFile | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; - if (parent.testType === TestType.file) { + if (parent.testType === TestingType.file) { return parent as TestFile; } } @@ -81,7 +81,7 @@ export function findParentSuite(parents: TestNode[]): TestSuite | undefined { // Iterate in reverse order. for (let i = parents.length; i > 0; i -= 1) { const parent = parents[i - 1]; - if (parent.testType === TestType.suite) { + if (parent.testType === TestingType.suite) { return parent as TestSuite; } } @@ -147,7 +147,7 @@ export namespace nodes { nameToRun: nameToRun || dirname, folders: [], testFiles: [], - testType: TestType.folder, + testType: TestingType.folder, // result time: 0, status: TestStatus.Unknown @@ -175,7 +175,7 @@ export namespace nodes { xmlName: xmlName!, suites: [], functions: [], - testType: TestType.file, + testType: TestingType.file, // result time: 0, status: TestStatus.Unknown @@ -199,7 +199,7 @@ export namespace nodes { isInstance: isInstance, suites: [], functions: [], - testType: TestType.suite, + testType: TestingType.suite, // result time: 0, status: TestStatus.Unknown @@ -217,7 +217,7 @@ export namespace nodes { name: name, nameToRun: nameToRun || name, subtestParent: subtestParent, - testType: TestType.function, + testType: TestingType.function, // result time: 0, status: TestStatus.Unknown @@ -346,7 +346,7 @@ namespace declarative { type ParsedTestNode = { indent: string; name: string; - testType: TestType; + testType: TestingType; result: TestResult; }; @@ -390,18 +390,18 @@ namespace declarative { resource ); switch (parsed.testType) { - case TestType.folder: + case TestingType.folder: tests.testFolders.push(node as TestFolder); break; - case TestType.file: + case TestingType.file: tests.testFiles.push(node as TestFile); break; - case TestType.suite: + case TestingType.suite: tests.testSuites.push( flattenSuite(node as TestSuite, parents) ); break; - case TestType.function: + case TestingType.function: // This does not deal with subtests? tests.testFunctions.push( flattenFunction(node as TestFunction, parents) @@ -438,10 +438,10 @@ namespace declarative { } // Determine the type from the name. - let testType: TestType; + let testType: TestingType; if (name.endsWith('/')) { // folder - testType = TestType.folder; + testType = TestingType.folder; while (name.endsWith('/')) { name = name.slice(0, -1); } @@ -450,24 +450,24 @@ namespace declarative { if (name.includes('/')) { throw Error('filename must not include directories'); } - testType = TestType.file; + testType = TestingType.file; } else if (name.startsWith('<')) { // suite if (!name.endsWith('>')) { throw Error('suite missing closing bracket'); } - testType = TestType.suite; + testType = TestingType.suite; name = name.slice(1, -1); } else { // test - testType = TestType.function; + testType = TestingType.function; } // Parse the results. const result: TestResult = { time: 0 }; - if (parts.length !== 0 && testType !== TestType.function) { + if (parts.length !== 0 && testType !== TestingType.function) { throw Error('non-test nodes do not have results'); } switch (parts.length) { @@ -519,7 +519,7 @@ namespace declarative { parsed: ParsedTestNode ): boolean { if (parsed.indent === '') { - if (parsed.testType !== TestType.folder) { + if (parsed.testType !== TestingType.folder) { throw Error('a top-level node must be a folder'); } return true; @@ -555,13 +555,13 @@ namespace declarative { function buildDiscoveredChildNode( parent: TestParent, name: string, - testType: TestType, + testType: TestingType, provider: TestProvider, resource?: Uri ): TestNode { switch (testType) { - case TestType.folder: - if (parent.testType !== TestType.folder) { + case TestingType.folder: + if (parent.testType !== TestingType.folder) { throw Error('parent must be a folder'); } return nodes.addDiscoveredSubFolder( @@ -570,8 +570,8 @@ namespace declarative { undefined, resource ); - case TestType.file: - if (parent.testType !== TestType.folder) { + case TestingType.file: + if (parent.testType !== TestingType.folder) { throw Error('parent must be a folder'); } return nodes.addDiscoveredFile( @@ -581,11 +581,11 @@ namespace declarative { undefined, resource ); - case TestType.suite: + case TestingType.suite: let suiteParent: TestFile | TestSuite; - if (parent.testType === TestType.file) { + if (parent.testType === TestingType.file) { suiteParent = parent as TestFile; - } else if (parent.testType === TestType.suite) { + } else if (parent.testType === TestingType.suite) { suiteParent = parent as TestSuite; } else { throw Error('parent must be a file or suite'); @@ -599,13 +599,13 @@ namespace declarative { undefined, resource ); - case TestType.function: + case TestingType.function: let funcParent: TestFile | TestSuite; - if (parent.testType === TestType.file) { + if (parent.testType === TestingType.file) { funcParent = parent as TestFile; - } else if (parent.testType === TestType.suite) { + } else if (parent.testType === TestingType.suite) { funcParent = parent as TestSuite; - } else if (parent.testType === TestType.function) { + } else if (parent.testType === TestingType.function) { throw Error('not finished: use addDiscoveredSubTest()'); } else { throw Error('parent must be a file, suite, or function'); From 62bd8ae743dbb30b0f39b66ce32f641ef4ed74c1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Sep 2019 11:02:38 -0600 Subject: [PATCH 29/30] Drop a dead comment. --- src/client/testing/common/xUnitParser.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts index a651b460d724..9467fb6674c8 100644 --- a/src/client/testing/common/xUnitParser.ts +++ b/src/client/testing/common/xUnitParser.ts @@ -180,7 +180,5 @@ function updateResultStatus( } else { result.status = TestStatus.Pass; result.passed = true; - //result.message = undefined; - //result.traceback = undefined; } } From 2d46d6a9c1d68dad1594bf117f558955b9405ad0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Sep 2019 13:52:40 -0600 Subject: [PATCH 30/30] Fix TestDataItemType. --- src/client/testing/common/types.ts | 8 ++++---- src/client/testing/types.ts | 10 +++++----- src/test/testing/common/testUtils.unit.test.ts | 2 +- .../testing/explorer/testTreeViewItem.unit.test.ts | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 48d715416c39..1e02fe6eb970 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -69,10 +69,10 @@ export type TestsToRun = { // test results export enum TestingType { - folder = 'testFolder', - file = 'testFile', - suite = 'testSuite', - function = 'testFunction' + folder = 'folder', + file = 'file', + suite = 'suite', + function = 'function' } export enum TestStatus { diff --git a/src/client/testing/types.ts b/src/client/testing/types.ts index b10999f1f175..082ab94a7a8e 100644 --- a/src/client/testing/types.ts +++ b/src/client/testing/types.ts @@ -158,11 +158,11 @@ export interface ILocationStackFrameDetails { export type WorkspaceTestStatus = { workspace: Uri; status: TestStatus }; export enum TestDataItemType { - workspaceFolder = 'testWorkspaceFolder', - folder = 'testFolder', - file = 'testFile', - suite = 'testSuite', - function = 'testFunction' + workspaceFolder = 'workspaceFolder', + folder = 'folder', + file = 'file', + suite = 'suite', + function = 'function' } export type TestDataItem = TestWorkspaceFolder | TestFolder | TestFile | TestSuite | TestFunction; diff --git a/src/test/testing/common/testUtils.unit.test.ts b/src/test/testing/common/testUtils.unit.test.ts index 24261379fae3..4e51af938051 100644 --- a/src/test/testing/common/testUtils.unit.test.ts +++ b/src/test/testing/common/testUtils.unit.test.ts @@ -89,7 +89,7 @@ export function createMockTestDataItem( case TestDataItemType.workspaceFolder: return (new TestWorkspaceFolder({ uri: Uri.file(''), name: 'a', index: 0 })) as T; default: - throw new Error('Unknown type'); + throw new Error(`Unknown type ${type}`); } } diff --git a/src/test/testing/explorer/testTreeViewItem.unit.test.ts b/src/test/testing/explorer/testTreeViewItem.unit.test.ts index df6a89cca393..e8521e62865e 100644 --- a/src/test/testing/explorer/testTreeViewItem.unit.test.ts +++ b/src/test/testing/explorer/testTreeViewItem.unit.test.ts @@ -31,27 +31,27 @@ suite('Unit Tests Test Explorer View Items', () => { test('Test root folder created into test view item', () => { const viewItem = new TestTreeItem(resource, testFolder); - expect(viewItem.contextValue).is.equal('testFolder'); + expect(viewItem.contextValue).is.equal('folder'); }); test('Test file created into test view item', () => { const viewItem = new TestTreeItem(resource, testFile); - expect(viewItem.contextValue).is.equal('testFile'); + expect(viewItem.contextValue).is.equal('file'); }); test('Test suite created into test view item', () => { const viewItem = new TestTreeItem(resource, testSuite); - expect(viewItem.contextValue).is.equal('testSuite'); + expect(viewItem.contextValue).is.equal('suite'); }); test('Test function created into test view item', () => { const viewItem = new TestTreeItem(resource, testFunction); - expect(viewItem.contextValue).is.equal('testFunction'); + expect(viewItem.contextValue).is.equal('function'); }); test('Test suite function created into test view item', () => { const viewItem = new TestTreeItem(resource, testSuiteFunction); - expect(viewItem.contextValue).is.equal('testFunction'); + expect(viewItem.contextValue).is.equal('function'); }); test('Test subtest parent created into test view item', () => { @@ -62,7 +62,7 @@ suite('Unit Tests Test Explorer View Items', () => { const viewItem = new TestTreeItem(resource, subtestParent.asSuite); - expect(viewItem.contextValue).is.equal('testSuite'); + expect(viewItem.contextValue).is.equal('suite'); expect(viewItem.command!.command).is.equal(Commands.navigateToTestFunction); }); @@ -71,6 +71,6 @@ suite('Unit Tests Test Explorer View Items', () => { const viewItem = new TestTreeItem(resource, testFunction); - expect(viewItem.contextValue).is.equal('testFunction'); + expect(viewItem.contextValue).is.equal('function'); }); });