remove auto conversion of XML attributes based on value

This commit is contained in:
Michal Dorner 2021-01-24 21:05:34 +01:00
parent 1a9ca69197
commit 40b5f476c7
No known key found for this signature in database
GPG key ID: 9EEE04B48DA36786
6 changed files with 44 additions and 55 deletions

View file

@ -5,8 +5,8 @@ import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types
import {parseStringPromise} from 'xml2js' import {parseStringPromise} from 'xml2js'
import {normalizeFilePath} from '../../utils/file-utils' import {normalizeFilePath} from '../../utils/file-utils'
import {parseAttribute} from '../../utils/xml-utils'
import {Icon, fixEol} from '../../utils/markdown-utils' import {Icon, fixEol} from '../../utils/markdown-utils'
import {parseIsoDate, parseNetDuration} from '../../utils/parse-utils'
import { import {
TestExecutionResult, TestExecutionResult,
@ -70,9 +70,7 @@ export async function parseDotnetTrx(files: FileContent[], options: ParseOptions
async function getTrxReport(file: FileContent): Promise<TrxReport> { async function getTrxReport(file: FileContent): Promise<TrxReport> {
core.info(`Parsing content of '${file.path}'`) core.info(`Parsing content of '${file.path}'`)
try { try {
return (await parseStringPromise(file.content, { return (await parseStringPromise(file.content)) as TrxReport
attrValueProcessors: [parseAttribute]
})) as TrxReport
} catch (e) { } catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`) throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
} }
@ -80,7 +78,7 @@ async function getTrxReport(file: FileContent): Promise<TrxReport> {
function getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult { function getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {
const times = trx.TestRun.Times[0].$ const times = trx.TestRun.Times[0].$
const totalTime = times.finish.getTime() - times.start.getTime() const totalTime = parseIsoDate(times.finish).getTime() - parseIsoDate(times.start).getTime()
const suites = testClasses.map(tc => { const suites = testClasses.map(tc => {
const tests = tc.tests.map(t => new TestCaseResult(t.name, t.result, t.duration)) const tests = tc.tests.map(t => new TestCaseResult(t.name, t.result, t.duration))
@ -113,7 +111,8 @@ function getTestClasses(trx: TrxReport): TestClass[] {
} }
const output = r.unitTestResult.Output const output = r.unitTestResult.Output
const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined
const test = new Test(r.testMethod.$.name, r.unitTestResult.$.outcome, r.unitTestResult.$.duration, error) const duration = parseNetDuration(r.unitTestResult.$.duration)
const test = new Test(r.testMethod.$.name, r.unitTestResult.$.outcome, duration, error)
tc.tests.push(test) tc.tests.push(test)
} }

View file

@ -10,10 +10,10 @@ export interface TestRun {
export interface Times { export interface Times {
$: { $: {
creation: Date creation: string
queuing: Date queuing: string
start: Date start: string
finish: Date finish: string
} }
} }
@ -43,7 +43,7 @@ export interface UnitTestResult {
$: { $: {
testId: string testId: string
testName: string testName: string
duration: number duration: string
outcome: Outcome outcome: Outcome
} }
Output: Output[] Output: Output[]

View file

@ -5,7 +5,6 @@ import {parseStringPromise} from 'xml2js'
import {JunitReport, TestCase, TestSuite} from './jest-junit-types' import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
import {fixEol, Icon} from '../../utils/markdown-utils' import {fixEol, Icon} from '../../utils/markdown-utils'
import {normalizeFilePath} from '../../utils/file-utils' import {normalizeFilePath} from '../../utils/file-utils'
import {parseAttribute} from '../../utils/xml-utils'
import { import {
TestExecutionResult, TestExecutionResult,
@ -43,9 +42,7 @@ export async function parseJestJunit(files: FileContent[], options: ParseOptions
async function getJunitReport(file: FileContent): Promise<JunitReport> { async function getJunitReport(file: FileContent): Promise<JunitReport> {
core.info(`Parsing content of '${file.path}'`) core.info(`Parsing content of '${file.path}'`)
try { try {
return (await parseStringPromise(file.content, { return (await parseStringPromise(file.content)) as JunitReport
attrValueProcessors: [parseAttribute]
})) as JunitReport
} catch (e) { } catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`) throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
} }
@ -54,12 +51,12 @@ async function getJunitReport(file: FileContent): Promise<JunitReport> {
function getTestRunResult(path: string, junit: JunitReport): TestRunResult { function getTestRunResult(path: string, junit: JunitReport): TestRunResult {
const suites = junit.testsuites.testsuite.map(ts => { const suites = junit.testsuites.testsuite.map(ts => {
const name = ts.$.name.trim() const name = ts.$.name.trim()
const time = ts.$.time * 1000 const time = parseFloat(ts.$.time) * 1000
const sr = new TestSuiteResult(name, getGroups(ts), time) const sr = new TestSuiteResult(name, getGroups(ts), time)
return sr return sr
}) })
const time = junit.testsuites.$.time * 1000 const time = parseFloat(junit.testsuites.$.time) * 1000
return new TestRunResult(path, suites, time) return new TestRunResult(path, suites, time)
} }
@ -78,7 +75,7 @@ function getGroups(suite: TestSuite): TestGroupResult[] {
const tests = grp.tests.map(tc => { const tests = grp.tests.map(tc => {
const name = tc.$.name.trim() const name = tc.$.name.trim()
const result = getTestCaseResult(tc) const result = getTestCaseResult(tc)
const time = tc.$.time * 1000 const time = parseFloat(tc.$.time) * 1000
return new TestCaseResult(name, result, time) return new TestCaseResult(name, result, time)
}) })
return new TestGroupResult(grp.describe, tests) return new TestGroupResult(grp.describe, tests)

View file

@ -5,10 +5,10 @@ export interface JunitReport {
export interface TestSuites { export interface TestSuites {
$: { $: {
name: string name: string
tests: number tests: string
failures: number // assertion failed failures: string // assertion failed
errors: number // unhandled exception during test execution errors: string // unhandled exception during test execution
time: number time: string
} }
testsuite: TestSuite[] testsuite: TestSuite[]
} }
@ -16,11 +16,11 @@ export interface TestSuites {
export interface TestSuite { export interface TestSuite {
$: { $: {
name: string name: string
tests: number tests: string
errors: number errors: string
failures: number failures: string
skipped: number skipped: string
time: number time: string
timestamp?: Date timestamp?: Date
} }
testcase: TestCase[] testcase: TestCase[]
@ -31,7 +31,7 @@ export interface TestCase {
classname: string classname: string
file?: string file?: string
name: string name: string
time: number time: string
} }
failure?: string[] failure?: string[]
skipped?: string[] skipped?: string[]

20
src/utils/parse-utils.ts Normal file
View file

@ -0,0 +1,20 @@
export function parseNetDuration(str: string): number {
// matches dotnet duration: 00:00:00.0010000
const durationRe = /^(\d\d):(\d\d):(\d\d\.\d+)$/
const durationMatch = str.match(durationRe)
if (durationMatch === null) {
throw new Error(`Invalid format: "${str}" is not NET duration`)
}
const [_, hourStr, minStr, secStr] = durationMatch
return (parseInt(hourStr) * 3600 + parseInt(minStr) * 60 + parseFloat(secStr)) * 1000
}
export function parseIsoDate(str: string): Date {
const isoDateRe = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)$/
if (str === undefined || !isoDateRe.test(str)) {
throw new Error(`Invalid format: "${str}" is not ISO date`)
}
return new Date(str)
}

View file

@ -1,27 +0,0 @@
const isoDateRe = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)$/
// matches dotnet duration: 00:00:00.0010000
const durationRe = /^(\d\d):(\d\d):(\d\d\.\d+)$/
export function parseAttribute(str: string | undefined): string | Date | number | undefined {
if (str === '' || str === undefined) {
return str
}
if (isoDateRe.test(str)) {
return new Date(str)
}
const durationMatch = str.match(durationRe)
if (durationMatch !== null) {
const [_, hourStr, minStr, secStr] = durationMatch
return (parseInt(hourStr) * 3600 + parseInt(minStr) * 60 + parseFloat(secStr)) * 1000
}
const num = parseFloat(str)
if (isNaN(num)) {
return str
}
return num
}