mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-16 06:17:10 +01:00
Add support for mocha-json
This commit is contained in:
parent
f285c4c6d7
commit
9b675bd55f
21 changed files with 1588 additions and 59 deletions
|
|
@ -13,6 +13,7 @@ import {getReport} from './report/get-report'
|
|||
import {DartJsonParser} from './parsers/dart-json/dart-json-parser'
|
||||
import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser'
|
||||
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
||||
|
||||
import {normalizeDirPath} from './utils/path-utils'
|
||||
import {getCheckRunContext} from './utils/github-utils'
|
||||
|
|
@ -186,6 +187,8 @@ class TestReporter {
|
|||
return new DartJsonParser(options, 'flutter')
|
||||
case 'jest-junit':
|
||||
return new JestJunitParser(options)
|
||||
case 'mocha-json':
|
||||
return new MochaJsonParser(options)
|
||||
default:
|
||||
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export class DotnetTrxParser implements TestParser {
|
|||
const trx = await this.getTrxReport(path, content)
|
||||
const tc = this.getTestClasses(trx)
|
||||
const tr = this.getTestRunResult(path, trx, tc)
|
||||
tr.sort(true)
|
||||
return tr
|
||||
}
|
||||
|
||||
|
|
@ -88,11 +89,6 @@ export class DotnetTrxParser implements TestParser {
|
|||
}
|
||||
|
||||
const result = Object.values(testClasses)
|
||||
result.sort((a, b) => a.name.localeCompare(b.name))
|
||||
for (const tc of result) {
|
||||
tc.tests.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {ParseOptions, TestParser} from '../../test-parser'
|
|||
import {parseStringPromise} from 'xml2js'
|
||||
|
||||
import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
|
||||
import {getExceptionSource} from '../../utils/node-utils'
|
||||
import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
|
||||
|
||||
import {
|
||||
|
|
@ -81,7 +82,7 @@ export class JestJunitParser implements TestParser {
|
|||
let path
|
||||
let line
|
||||
|
||||
const src = this.exceptionThrowSource(details)
|
||||
const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
|
||||
if (src) {
|
||||
path = src.path
|
||||
line = src.line
|
||||
|
|
@ -94,31 +95,13 @@ export class JestJunitParser implements TestParser {
|
|||
}
|
||||
}
|
||||
|
||||
private exceptionThrowSource(stackTrace: string): {path: string; line: number} | undefined {
|
||||
const lines = stackTrace.split(/\r?\n/)
|
||||
const re = /\((.*):(\d+):\d+\)$/
|
||||
|
||||
const {trackedFiles} = this.options
|
||||
for (const str of lines) {
|
||||
const match = str.match(re)
|
||||
if (match !== null) {
|
||||
const [_, fileStr, lineStr] = match
|
||||
const filePath = normalizeFilePath(fileStr)
|
||||
if (filePath.startsWith('internal/') || filePath.includes('/node_modules/')) {
|
||||
continue
|
||||
}
|
||||
const workDir = this.getWorkDir(filePath)
|
||||
if (!workDir) {
|
||||
continue
|
||||
}
|
||||
const path = filePath.substr(workDir.length)
|
||||
if (trackedFiles.includes(path)) {
|
||||
const line = parseInt(lineStr)
|
||||
|
||||
return {path, line}
|
||||
}
|
||||
}
|
||||
private getRelativePath(path: string): string {
|
||||
path = normalizeFilePath(path)
|
||||
const workDir = this.getWorkDir(path)
|
||||
if (workDir !== undefined && path.startsWith(workDir)) {
|
||||
path = path.substr(workDir.length)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
private getWorkDir(path: string): string | undefined {
|
||||
|
|
|
|||
118
src/parsers/mocha-json/mocha-json-parser.ts
Normal file
118
src/parsers/mocha-json/mocha-json-parser.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import {ParseOptions, TestParser} from '../../test-parser'
|
||||
import {
|
||||
TestCaseError,
|
||||
TestCaseResult,
|
||||
TestExecutionResult,
|
||||
TestGroupResult,
|
||||
TestRunResult,
|
||||
TestSuiteResult
|
||||
} from '../../test-results'
|
||||
import {getExceptionSource} from '../../utils/node-utils'
|
||||
import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
|
||||
import {MochaJson, MochaJsonTest} from './mocha-json-types'
|
||||
|
||||
export class MochaJsonParser implements TestParser {
|
||||
assumedWorkDir: string | undefined
|
||||
|
||||
constructor(readonly options: ParseOptions) {}
|
||||
|
||||
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||
const mocha = this.getMochaJson(path, content)
|
||||
const result = this.getTestRunResult(path, mocha)
|
||||
result.sort(true)
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
private getMochaJson(path: string, content: string): MochaJson {
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid JSON at ${path}\n\n${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getTestRunResult(resultsPath: string, mocha: MochaJson): TestRunResult {
|
||||
const suitesMap: {[path: string]: TestSuiteResult} = {}
|
||||
|
||||
const getSuite = (test: MochaJsonTest): TestSuiteResult => {
|
||||
const path = this.getRelativePath(test.file)
|
||||
return suitesMap[path] ?? (suitesMap[path] = new TestSuiteResult(path, []))
|
||||
}
|
||||
|
||||
for (const test of mocha.passes) {
|
||||
const suite = getSuite(test)
|
||||
this.processTest(suite, test, 'success')
|
||||
}
|
||||
|
||||
for (const test of mocha.failures) {
|
||||
const suite = getSuite(test)
|
||||
this.processTest(suite, test, 'failed')
|
||||
}
|
||||
|
||||
for (const test of mocha.pending) {
|
||||
const suite = getSuite(test)
|
||||
this.processTest(suite, test, 'skipped')
|
||||
}
|
||||
|
||||
const suites = Object.values(suitesMap)
|
||||
return new TestRunResult(resultsPath, suites, mocha.stats.duration)
|
||||
}
|
||||
|
||||
private processTest(suite: TestSuiteResult, test: MochaJsonTest, result: TestExecutionResult): void {
|
||||
const groupName =
|
||||
test.fullTitle !== test.title
|
||||
? test.fullTitle.substr(0, test.fullTitle.length - test.title.length).trimEnd()
|
||||
: null
|
||||
|
||||
let group = suite.groups.find(grp => grp.name === groupName)
|
||||
if (group === undefined) {
|
||||
group = new TestGroupResult(groupName, [])
|
||||
suite.groups.push(group)
|
||||
}
|
||||
|
||||
const error = this.getTestCaseError(test)
|
||||
const testCase = new TestCaseResult(test.title, result, test.duration, error)
|
||||
group.tests.push(testCase)
|
||||
}
|
||||
|
||||
private getTestCaseError(test: MochaJsonTest): TestCaseError | undefined {
|
||||
const details = test.err.stack
|
||||
const message = test.err.message
|
||||
if (details === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let path
|
||||
let line
|
||||
|
||||
const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
|
||||
if (src) {
|
||||
path = src.path
|
||||
line = src.line
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
line,
|
||||
message,
|
||||
details
|
||||
}
|
||||
}
|
||||
|
||||
private getRelativePath(path: string): string {
|
||||
path = normalizeFilePath(path)
|
||||
const workDir = this.getWorkDir(path)
|
||||
if (workDir !== undefined && path.startsWith(workDir)) {
|
||||
path = path.substr(workDir.length)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
private getWorkDir(path: string): string | undefined {
|
||||
return (
|
||||
this.options.workDir ??
|
||||
this.assumedWorkDir ??
|
||||
(this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
|
||||
)
|
||||
}
|
||||
}
|
||||
23
src/parsers/mocha-json/mocha-json-types.ts
Normal file
23
src/parsers/mocha-json/mocha-json-types.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export interface MochaJson {
|
||||
stats: MochaJsonStats
|
||||
passes: MochaJsonTest[]
|
||||
pending: MochaJsonTest[]
|
||||
failures: MochaJsonTest[]
|
||||
}
|
||||
|
||||
export interface MochaJsonStats {
|
||||
duration: number
|
||||
}
|
||||
|
||||
export interface MochaJsonTest {
|
||||
title: string
|
||||
fullTitle: string
|
||||
file: string
|
||||
duration: number
|
||||
err: MochaJsonTestError
|
||||
}
|
||||
|
||||
export interface MochaJsonTestError {
|
||||
stack?: string
|
||||
message?: string
|
||||
}
|
||||
|
|
@ -26,6 +26,15 @@ export class TestRunResult {
|
|||
get failedSuites(): TestSuiteResult[] {
|
||||
return this.suites.filter(s => s.result === 'failed')
|
||||
}
|
||||
|
||||
sort(deep: boolean): void {
|
||||
this.suites.sort((a, b) => a.name.localeCompare(b.name))
|
||||
if (deep) {
|
||||
for (const suite of this.suites) {
|
||||
suite.sort(deep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TestSuiteResult {
|
||||
|
|
@ -55,6 +64,15 @@ export class TestSuiteResult {
|
|||
get failedGroups(): TestGroupResult[] {
|
||||
return this.groups.filter(grp => grp.result === 'failed')
|
||||
}
|
||||
|
||||
sort(deep: boolean): void {
|
||||
this.groups.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
|
||||
if (deep) {
|
||||
for (const grp of this.groups) {
|
||||
grp.sort()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TestGroupResult {
|
||||
|
|
@ -80,6 +98,10 @@ export class TestGroupResult {
|
|||
get failedTests(): TestCaseResult[] {
|
||||
return this.tests.filter(tc => tc.result === 'failed')
|
||||
}
|
||||
|
||||
sort(): void {
|
||||
this.tests.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
}
|
||||
|
||||
export class TestCaseResult {
|
||||
|
|
|
|||
30
src/utils/node-utils.ts
Normal file
30
src/utils/node-utils.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import {normalizeFilePath} from './path-utils'
|
||||
|
||||
export function getExceptionSource(
|
||||
stackTrace: string,
|
||||
trackedFiles: string[],
|
||||
getRelativePath: (str: string) => string
|
||||
): {path: string; line: number} | undefined {
|
||||
const lines = stackTrace.split(/\r?\n/)
|
||||
const re = /\((.*):(\d+):\d+\)$/
|
||||
|
||||
for (const str of lines) {
|
||||
const match = str.match(re)
|
||||
if (match !== null) {
|
||||
const [_, fileStr, lineStr] = match
|
||||
const filePath = normalizeFilePath(fileStr)
|
||||
if (filePath.startsWith('internal/') || filePath.includes('/node_modules/')) {
|
||||
continue
|
||||
}
|
||||
const path = getRelativePath(filePath)
|
||||
if (!path) {
|
||||
continue
|
||||
}
|
||||
if (trackedFiles.includes(path)) {
|
||||
const line = parseInt(lineStr)
|
||||
|
||||
return {path, line}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue