mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-16 06:17:10 +01:00
126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
import {Annotation, ParseOptions, TestResult} from '../parser-types'
|
|
import {parseStringPromise} from 'xml2js'
|
|
|
|
import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
|
|
import {fixEol, Icon} from '../../utils/markdown-utils'
|
|
import {normalizeFilePath} from '../../utils/file-utils'
|
|
import {parseAttribute} from '../../utils/xml-utils'
|
|
|
|
import {
|
|
TestExecutionResult,
|
|
TestRunResult,
|
|
TestSuiteResult,
|
|
TestGroupResult,
|
|
TestCaseResult
|
|
} from '../../report/test-results'
|
|
import getReport from '../../report/get-report'
|
|
|
|
export async function parseJestJunit(content: string, options: ParseOptions): Promise<TestResult> {
|
|
const junit = (await parseStringPromise(content, {
|
|
attrValueProcessors: [parseAttribute]
|
|
})) as JunitReport
|
|
const testsuites = junit.testsuites
|
|
const success = !(testsuites.$?.failures > 0 || testsuites.$?.errors > 0)
|
|
const icon = success ? Icon.success : Icon.fail
|
|
|
|
return {
|
|
success,
|
|
output: {
|
|
title: `${options.name.trim()} ${icon}`,
|
|
summary: getSummary(junit),
|
|
annotations: options.annotations ? getAnnotations(junit, options.workDir, options.trackedFiles) : undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSummary(junit: JunitReport): string {
|
|
const suites = junit.testsuites.testsuite.map(ts => {
|
|
const name = ts.$.name.trim()
|
|
const time = ts.$.time * 1000
|
|
const sr = new TestSuiteResult(name, getGroups(ts), time)
|
|
return sr
|
|
})
|
|
|
|
const time = junit.testsuites.$.time * 1000
|
|
const tr = new TestRunResult(suites, time)
|
|
return getReport(tr)
|
|
}
|
|
|
|
function getGroups(suite: TestSuite): TestGroupResult[] {
|
|
const groups: {describe: string; tests: TestCase[]}[] = []
|
|
for (const tc of suite.testcase) {
|
|
let grp = groups.find(g => g.describe === tc.$.classname)
|
|
if (grp === undefined) {
|
|
grp = {describe: tc.$.classname, tests: []}
|
|
groups.push(grp)
|
|
}
|
|
grp.tests.push(tc)
|
|
}
|
|
|
|
return groups.map(grp => {
|
|
const tests = grp.tests.map(tc => {
|
|
const name = tc.$.name.trim()
|
|
const result = getTestCaseResult(tc)
|
|
const time = tc.$.time * 1000
|
|
return new TestCaseResult(name, result, time)
|
|
})
|
|
return new TestGroupResult(grp.describe, tests)
|
|
})
|
|
}
|
|
|
|
function getTestCaseResult(test: TestCase): TestExecutionResult {
|
|
if (test.failure) return 'failed'
|
|
if (test.skipped) return 'skipped'
|
|
return 'success'
|
|
}
|
|
|
|
function getAnnotations(junit: JunitReport, workDir: string, trackedFiles: string[]): Annotation[] {
|
|
const annotations: Annotation[] = []
|
|
for (const suite of junit.testsuites.testsuite) {
|
|
for (const tc of suite.testcase) {
|
|
if (!tc.failure) {
|
|
continue
|
|
}
|
|
for (const ex of tc.failure) {
|
|
const src = exceptionThrowSource(ex, workDir, trackedFiles)
|
|
if (src === null) {
|
|
continue
|
|
}
|
|
annotations.push({
|
|
annotation_level: 'failure',
|
|
start_line: src.line,
|
|
end_line: src.line,
|
|
path: src.file,
|
|
message: fixEol(ex),
|
|
title: `[${suite.$.name}] ${tc.$.name.trim()}`
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return annotations
|
|
}
|
|
|
|
export function exceptionThrowSource(
|
|
ex: string,
|
|
workDir: string,
|
|
trackedFiles: string[]
|
|
): {file: string; line: number; column: number} | null {
|
|
const lines = ex.split(/\r?\n/)
|
|
const re = /\((.*):(\d+):(\d+)\)$/
|
|
|
|
for (const str of lines) {
|
|
const match = str.match(re)
|
|
if (match !== null) {
|
|
const [_, fileStr, lineStr, colStr] = match
|
|
const filePath = normalizeFilePath(fileStr)
|
|
const file = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
|
if (trackedFiles.includes(file)) {
|
|
const line = parseInt(lineStr)
|
|
const column = parseInt(colStr)
|
|
return {file, line, column}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|