mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-17 06:47:09 +01:00
Add GoJunit Parser
This commit is contained in:
parent
15c2a59c29
commit
39eab1c454
7 changed files with 32568 additions and 1 deletions
32220
__tests__/__snapshots__/go-junit.test.ts.snap
Normal file
32220
__tests__/__snapshots__/go-junit.test.ts.snap
Normal file
File diff suppressed because it is too large
Load diff
69
__tests__/go-junit.test.ts
Normal file
69
__tests__/go-junit.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
import {GoJunitParser} from '../src/parsers/go-junit/go-junit-parser'
|
||||||
|
import {ParseOptions} from '../src/test-parser'
|
||||||
|
import {getReport} from '../src/report/get-report'
|
||||||
|
import {normalizeFilePath} from '../src/utils/path-utils'
|
||||||
|
|
||||||
|
describe('jest-junit tests', () => {
|
||||||
|
it('produces empty test run result when there are no test cases', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'jest-junit.xml')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
parseErrors: true,
|
||||||
|
trackedFiles: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new GoJunitParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result.tests).toBe(0)
|
||||||
|
expect(result.result).toBe('success')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('report from ./reports/jest test results matches snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'jest-junit.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
parseErrors: true,
|
||||||
|
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js']
|
||||||
|
//workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new GoJunitParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
|
const report = getReport([result])
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('report from facebook/jest test results matches snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'jest', 'jest-test-results.xml')
|
||||||
|
const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'jest', 'files.txt')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'jest-test-results.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
parseErrors: true,
|
||||||
|
trackedFiles
|
||||||
|
//workDir: '/home/dorny/dorny/jest/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new GoJunitParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
|
const report = getReport([result])
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
|
})
|
||||||
|
})
|
||||||
122
dist/index.js
generated
vendored
122
dist/index.js
generated
vendored
|
|
@ -265,6 +265,7 @@ const dotnet_trx_parser_1 = __nccwpck_require__(2664);
|
||||||
const java_junit_parser_1 = __nccwpck_require__(676);
|
const java_junit_parser_1 = __nccwpck_require__(676);
|
||||||
const jest_junit_parser_1 = __nccwpck_require__(1113);
|
const jest_junit_parser_1 = __nccwpck_require__(1113);
|
||||||
const mocha_json_parser_1 = __nccwpck_require__(6043);
|
const mocha_json_parser_1 = __nccwpck_require__(6043);
|
||||||
|
const go_junit_parser_1 = __nccwpck_require__(4207);
|
||||||
const path_utils_1 = __nccwpck_require__(4070);
|
const path_utils_1 = __nccwpck_require__(4070);
|
||||||
const github_utils_1 = __nccwpck_require__(3522);
|
const github_utils_1 = __nccwpck_require__(3522);
|
||||||
const markdown_utils_1 = __nccwpck_require__(6482);
|
const markdown_utils_1 = __nccwpck_require__(6482);
|
||||||
|
|
@ -423,6 +424,8 @@ class TestReporter {
|
||||||
return new jest_junit_parser_1.JestJunitParser(options);
|
return new jest_junit_parser_1.JestJunitParser(options);
|
||||||
case 'mocha-json':
|
case 'mocha-json':
|
||||||
return new mocha_json_parser_1.MochaJsonParser(options);
|
return new mocha_json_parser_1.MochaJsonParser(options);
|
||||||
|
case 'go-junit':
|
||||||
|
return new go_junit_parser_1.GoJunitParser(options);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`);
|
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`);
|
||||||
}
|
}
|
||||||
|
|
@ -885,6 +888,125 @@ class DotnetTrxParser {
|
||||||
exports.DotnetTrxParser = DotnetTrxParser;
|
exports.DotnetTrxParser = DotnetTrxParser;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 4207:
|
||||||
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.GoJunitParser = void 0;
|
||||||
|
const xml2js_1 = __nccwpck_require__(6189);
|
||||||
|
const node_utils_1 = __nccwpck_require__(5824);
|
||||||
|
const path_utils_1 = __nccwpck_require__(4070);
|
||||||
|
const test_results_1 = __nccwpck_require__(2768);
|
||||||
|
class GoJunitParser {
|
||||||
|
constructor(options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
parse(path, content) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const ju = yield this.getJunitReport(path, content);
|
||||||
|
return this.getTestRunResult(path, ju);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getJunitReport(path, content) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
return (yield (0, xml2js_1.parseStringPromise)(content));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error(`Invalid XML at ${path}\n\n${e}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getTestRunResult(path, junit) {
|
||||||
|
const suites = junit.testsuites.testsuite === undefined
|
||||||
|
? []
|
||||||
|
: junit.testsuites.testsuite.map(ts => {
|
||||||
|
const name = ts.$.name.trim();
|
||||||
|
const time = parseFloat(ts.$.time) * 1000;
|
||||||
|
const sr = new test_results_1.TestSuiteResult(name, this.getGroups(ts), time);
|
||||||
|
return sr;
|
||||||
|
});
|
||||||
|
const time = parseFloat(junit.testsuites.$.time) * 1000;
|
||||||
|
return new test_results_1.TestRunResult(path, suites, time);
|
||||||
|
}
|
||||||
|
getGroups(suite) {
|
||||||
|
const groups = [];
|
||||||
|
const returnEmpytGroups = [];
|
||||||
|
if (!suite.testcase)
|
||||||
|
return returnEmpytGroups;
|
||||||
|
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 = this.getTestCaseResult(tc);
|
||||||
|
const time = parseFloat(tc.$.time) * 1000;
|
||||||
|
const error = this.getTestCaseError(tc);
|
||||||
|
return new test_results_1.TestCaseResult(name, result, time, error);
|
||||||
|
});
|
||||||
|
return new test_results_1.TestGroupResult(grp.describe, tests);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getTestCaseResult(test) {
|
||||||
|
if (test.failure)
|
||||||
|
return 'failed';
|
||||||
|
if (test.skipped)
|
||||||
|
return 'skipped';
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
getTestCaseError(tc) {
|
||||||
|
if (!this.options.parseErrors || !tc.failure) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const details = tc.failure[0];
|
||||||
|
let path;
|
||||||
|
let line;
|
||||||
|
const src = (0, node_utils_1.getExceptionSource)(details, this.options.trackedFiles, file => this.getRelativePath(file));
|
||||||
|
if (src) {
|
||||||
|
path = src.path;
|
||||||
|
line = src.line;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
line,
|
||||||
|
details
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getRelativePath(path) {
|
||||||
|
path = (0, path_utils_1.normalizeFilePath)(path);
|
||||||
|
const workDir = this.getWorkDir(path);
|
||||||
|
if (workDir !== undefined && path.startsWith(workDir)) {
|
||||||
|
path = path.substr(workDir.length);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
getWorkDir(path) {
|
||||||
|
var _a, _b;
|
||||||
|
return ((_b = (_a = this.options.workDir) !== null && _a !== void 0 ? _a : this.assumedWorkDir) !== null && _b !== void 0 ? _b : (this.assumedWorkDir = (0, path_utils_1.getBasePath)(path, this.options.trackedFiles)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.GoJunitParser = GoJunitParser;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 676:
|
/***/ 676:
|
||||||
|
|
|
||||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
|
|
@ -15,6 +15,7 @@ import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser'
|
||||||
import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
|
import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
|
||||||
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||||
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
||||||
|
import {GoJunitParser} from './parsers/go-junit/go-junit-parser'
|
||||||
|
|
||||||
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
|
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
|
||||||
import {getCheckRunContext} from './utils/github-utils'
|
import {getCheckRunContext} from './utils/github-utils'
|
||||||
|
|
@ -213,6 +214,8 @@ class TestReporter {
|
||||||
return new JestJunitParser(options)
|
return new JestJunitParser(options)
|
||||||
case 'mocha-json':
|
case 'mocha-json':
|
||||||
return new MochaJsonParser(options)
|
return new MochaJsonParser(options)
|
||||||
|
case 'go-junit':
|
||||||
|
return new GoJunitParser(options)
|
||||||
default:
|
default:
|
||||||
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
src/parsers/go-junit/go-junit-parser.ts
Normal file
119
src/parsers/go-junit/go-junit-parser.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import {ParseOptions, TestParser} from '../../test-parser'
|
||||||
|
import {parseStringPromise} from 'xml2js'
|
||||||
|
|
||||||
|
import {JunitReport, TestCase, TestSuite} from './go-junit-types'
|
||||||
|
import {getExceptionSource} from '../../utils/node-utils'
|
||||||
|
import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
|
||||||
|
|
||||||
|
import {
|
||||||
|
TestExecutionResult,
|
||||||
|
TestRunResult,
|
||||||
|
TestSuiteResult,
|
||||||
|
TestGroupResult,
|
||||||
|
TestCaseResult,
|
||||||
|
TestCaseError
|
||||||
|
} from '../../test-results'
|
||||||
|
|
||||||
|
export class GoJunitParser implements TestParser {
|
||||||
|
assumedWorkDir: string | undefined
|
||||||
|
|
||||||
|
constructor(readonly options: ParseOptions) {}
|
||||||
|
|
||||||
|
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||||
|
const ju = await this.getJunitReport(path, content)
|
||||||
|
return this.getTestRunResult(path, ju)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getJunitReport(path: string, content: string): Promise<JunitReport> {
|
||||||
|
try {
|
||||||
|
return (await parseStringPromise(content)) as JunitReport
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Invalid XML at ${path}\n\n${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestRunResult(path: string, junit: JunitReport): TestRunResult {
|
||||||
|
const suites =
|
||||||
|
junit.testsuites.testsuite === undefined
|
||||||
|
? []
|
||||||
|
: junit.testsuites.testsuite.map(ts => {
|
||||||
|
const name = ts.$.name.trim()
|
||||||
|
const time = parseFloat(ts.$.time) * 1000
|
||||||
|
const sr = new TestSuiteResult(name, this.getGroups(ts), time)
|
||||||
|
return sr
|
||||||
|
})
|
||||||
|
|
||||||
|
const time = parseFloat(junit.testsuites.$.time) * 1000
|
||||||
|
return new TestRunResult(path, suites, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getGroups(suite: TestSuite): TestGroupResult[] {
|
||||||
|
const groups: {describe: string; tests: TestCase[]}[] = []
|
||||||
|
const returnEmpytGroups: TestGroupResult[] = [];
|
||||||
|
if (!suite.testcase) return returnEmpytGroups;
|
||||||
|
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 = this.getTestCaseResult(tc)
|
||||||
|
const time = parseFloat(tc.$.time) * 1000
|
||||||
|
const error = this.getTestCaseError(tc)
|
||||||
|
return new TestCaseResult(name, result, time, error)
|
||||||
|
})
|
||||||
|
return new TestGroupResult(grp.describe, tests)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestCaseResult(test: TestCase): TestExecutionResult {
|
||||||
|
if (test.failure) return 'failed'
|
||||||
|
if (test.skipped) return 'skipped'
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
|
||||||
|
if (!this.options.parseErrors || !tc.failure) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = tc.failure[0]
|
||||||
|
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,
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/parsers/go-junit/go-junit-types.ts
Normal file
34
src/parsers/go-junit/go-junit-types.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
export interface JunitReport {
|
||||||
|
testsuites: TestSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestSuites {
|
||||||
|
$: {
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
testsuite?: TestSuite[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestSuite {
|
||||||
|
$: {
|
||||||
|
name: string
|
||||||
|
tests: string
|
||||||
|
errors: string
|
||||||
|
failures: string
|
||||||
|
skipped: string
|
||||||
|
time: string
|
||||||
|
timestamp?: Date
|
||||||
|
}
|
||||||
|
testcase: TestCase[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestCase {
|
||||||
|
$: {
|
||||||
|
classname: string
|
||||||
|
file?: string
|
||||||
|
name: string
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
failure?: string[]
|
||||||
|
skipped?: string[]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue