mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-15 22:07:09 +01:00
Merge branch 'main' into mocha-json
This commit is contained in:
commit
3768e4e756
30 changed files with 14928 additions and 558 deletions
267
dist/index.js
generated
vendored
267
dist/index.js
generated
vendored
|
|
@ -219,6 +219,7 @@ const get_annotations_1 = __nccwpck_require__(5867);
|
|||
const get_report_1 = __nccwpck_require__(3737);
|
||||
const dart_json_parser_1 = __nccwpck_require__(4528);
|
||||
const dotnet_trx_parser_1 = __nccwpck_require__(2664);
|
||||
const java_junit_parser_1 = __nccwpck_require__(676);
|
||||
const jest_junit_parser_1 = __nccwpck_require__(1113);
|
||||
const mocha_json_parser_1 = __nccwpck_require__(6043);
|
||||
const path_utils_1 = __nccwpck_require__(4070);
|
||||
|
|
@ -358,6 +359,8 @@ class TestReporter {
|
|||
return new dotnet_trx_parser_1.DotnetTrxParser(options);
|
||||
case 'flutter-json':
|
||||
return new dart_json_parser_1.DartJsonParser(options, 'flutter');
|
||||
case 'java-junit':
|
||||
return new java_junit_parser_1.JavaJunitParser(options);
|
||||
case 'jest-junit':
|
||||
return new jest_junit_parser_1.JestJunitParser(options);
|
||||
case 'mocha-json':
|
||||
|
|
@ -673,6 +676,9 @@ class DotnetTrxParser {
|
|||
}
|
||||
getTestClasses(trx) {
|
||||
var _a;
|
||||
if (trx.TestRun.TestDefinitions === undefined || trx.TestRun.Results === undefined) {
|
||||
return [];
|
||||
}
|
||||
const unitTests = {};
|
||||
for (const td of trx.TestRun.TestDefinitions) {
|
||||
for (const ut of td.UnitTest) {
|
||||
|
|
@ -760,6 +766,206 @@ class DotnetTrxParser {
|
|||
exports.DotnetTrxParser = DotnetTrxParser;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 676:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.JavaJunitParser = void 0;
|
||||
const path = __importStar(__nccwpck_require__(5622));
|
||||
const xml2js_1 = __nccwpck_require__(6189);
|
||||
const path_utils_1 = __nccwpck_require__(4070);
|
||||
const test_results_1 = __nccwpck_require__(2768);
|
||||
class JavaJunitParser {
|
||||
constructor(options) {
|
||||
var _a;
|
||||
this.options = options;
|
||||
// Map to efficient lookup of all paths with given file name
|
||||
this.trackedFiles = {};
|
||||
for (const filePath of options.trackedFiles) {
|
||||
const fileName = path.basename(filePath);
|
||||
const files = (_a = this.trackedFiles[fileName]) !== null && _a !== void 0 ? _a : (this.trackedFiles[fileName] = []);
|
||||
files.push(path_utils_1.normalizeFilePath(filePath));
|
||||
}
|
||||
}
|
||||
async parse(filePath, content) {
|
||||
const reportOrSuite = await this.getJunitReport(filePath, content);
|
||||
const isReport = reportOrSuite.testsuites !== undefined;
|
||||
// XML might contain:
|
||||
// - multiple suites under <testsuites> root node
|
||||
// - single <testsuite> as root node
|
||||
let ju;
|
||||
if (isReport) {
|
||||
ju = reportOrSuite;
|
||||
}
|
||||
else {
|
||||
// Make it behave the same way as if suite was inside <testsuites> root node
|
||||
const suite = reportOrSuite.testsuite;
|
||||
ju = {
|
||||
testsuites: {
|
||||
$: { time: suite.$.time },
|
||||
testsuite: [suite]
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.getTestRunResult(filePath, ju);
|
||||
}
|
||||
async getJunitReport(filePath, content) {
|
||||
try {
|
||||
return await xml2js_1.parseStringPromise(content);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Invalid XML at ${filePath}\n\n${e}`);
|
||||
}
|
||||
}
|
||||
getTestRunResult(filePath, 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(filePath, suites, time);
|
||||
}
|
||||
getGroups(suite) {
|
||||
if (suite.testcase === undefined) {
|
||||
return [];
|
||||
}
|
||||
const groups = [];
|
||||
for (const tc of suite.testcase) {
|
||||
// Normally classname is same as suite name - both refer to same Java class
|
||||
// Therefore it doesn't make sense to process it as a group
|
||||
// and tests will be added to default group with empty name
|
||||
const className = tc.$.classname === suite.$.name ? '' : tc.$.classname;
|
||||
let grp = groups.find(g => g.name === className);
|
||||
if (grp === undefined) {
|
||||
grp = { name: 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.name, 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 failure = tc.failure[0];
|
||||
const details = failure._;
|
||||
let filePath;
|
||||
let line;
|
||||
const src = this.exceptionThrowSource(details);
|
||||
if (src) {
|
||||
filePath = src.filePath;
|
||||
line = src.line;
|
||||
}
|
||||
return {
|
||||
path: filePath,
|
||||
line,
|
||||
details,
|
||||
message: failure.message
|
||||
};
|
||||
}
|
||||
exceptionThrowSource(stackTrace) {
|
||||
const lines = stackTrace.split(/\r?\n/);
|
||||
const re = /^at (.*)\((.*):(\d+)\)$/;
|
||||
for (const str of lines) {
|
||||
const match = str.match(re);
|
||||
if (match !== null) {
|
||||
const [_, tracePath, fileName, lineStr] = match;
|
||||
const filePath = this.getFilePath(tracePath, fileName);
|
||||
if (filePath !== undefined) {
|
||||
const line = parseInt(lineStr);
|
||||
return { filePath, line };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stacktrace in Java doesn't contain full paths to source file.
|
||||
// There are only package, file name and line.
|
||||
// Assuming folder structure matches package name (as it should in Java),
|
||||
// we can try to match tracked file.
|
||||
getFilePath(tracePath, fileName) {
|
||||
// Check if there is any tracked file with given name
|
||||
const files = this.trackedFiles[fileName];
|
||||
if (files === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Remove class name and method name from trace.
|
||||
// Take parts until first item with capital letter - package names are lowercase while class name is CamelCase.
|
||||
const packageParts = tracePath.split(/\./g);
|
||||
const packageIndex = packageParts.findIndex(part => part[0] <= 'Z');
|
||||
if (packageIndex !== -1) {
|
||||
packageParts.splice(packageIndex, packageParts.length - packageIndex);
|
||||
}
|
||||
if (packageParts.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// Get right file
|
||||
// - file name matches
|
||||
// - parent folders structure must reflect the package name
|
||||
for (const filePath of files) {
|
||||
const dirs = path.dirname(filePath).split(/\//g);
|
||||
if (packageParts.length > dirs.length) {
|
||||
continue;
|
||||
}
|
||||
// get only N parent folders, where N = length of package name parts
|
||||
if (dirs.length > packageParts.length) {
|
||||
dirs.splice(0, dirs.length - packageParts.length);
|
||||
}
|
||||
// check if parent folder structure matches package name
|
||||
const isMatch = packageParts.every((part, i) => part === dirs[i]);
|
||||
if (isMatch) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
exports.JavaJunitParser = JavaJunitParser;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1113:
|
||||
|
|
@ -790,12 +996,14 @@ class JestJunitParser {
|
|||
}
|
||||
}
|
||||
getTestRunResult(path, junit) {
|
||||
const suites = 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 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);
|
||||
}
|
||||
|
|
@ -1201,7 +1409,9 @@ function getSuitesReport(tr, runIndex, options) {
|
|||
const icon = getResultIcon(tr.result);
|
||||
sections.push(`## ${nameLink} ${icon}`);
|
||||
const time = markdown_utils_1.formatTime(tr.time);
|
||||
const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.failed}** failed and **${tr.skipped}** skipped.`;
|
||||
const headingLine2 = tr.tests > 0
|
||||
? `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.failed}** failed and **${tr.skipped}** skipped.`
|
||||
: 'No tests found';
|
||||
sections.push(headingLine2);
|
||||
const suites = options.listSuites === 'failed' ? tr.failedSuites : tr.suites;
|
||||
if (suites.length > 0) {
|
||||
|
|
@ -1526,9 +1736,6 @@ function getCheckRunContext() {
|
|||
if (!event.workflow_run) {
|
||||
throw new Error("Event of type 'workflow_run' is missing 'workflow_run' field");
|
||||
}
|
||||
if (event.workflow_run.conclusion === 'cancelled') {
|
||||
throw new Error(`Workflow run ${event.workflow_run.id} has been cancelled`);
|
||||
}
|
||||
return {
|
||||
sha: event.workflow_run.head_commit.id,
|
||||
runId: event.workflow_run.id
|
||||
|
|
@ -1586,30 +1793,46 @@ async function downloadArtifact(octokit, artifactId, fileName, token) {
|
|||
}
|
||||
exports.downloadArtifact = downloadArtifact;
|
||||
async function listFiles(octokit, sha) {
|
||||
core.info('Fetching list of tracked files from GitHub');
|
||||
const commit = await octokit.git.getCommit({
|
||||
commit_sha: sha,
|
||||
...github.context.repo
|
||||
});
|
||||
const files = await listGitTree(octokit, commit.data.tree.sha, '');
|
||||
return files;
|
||||
core.startGroup('Fetching list of tracked files from GitHub');
|
||||
try {
|
||||
const commit = await octokit.git.getCommit({
|
||||
commit_sha: sha,
|
||||
...github.context.repo
|
||||
});
|
||||
const files = await listGitTree(octokit, commit.data.tree.sha, '');
|
||||
return files;
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
exports.listFiles = listFiles;
|
||||
async function listGitTree(octokit, sha, path) {
|
||||
const tree = await octokit.git.getTree({
|
||||
const pathLog = path ? ` at ${path}` : '';
|
||||
core.info(`Fetching tree ${sha}${pathLog}`);
|
||||
let truncated = false;
|
||||
let tree = await octokit.git.getTree({
|
||||
recursive: 'true',
|
||||
tree_sha: sha,
|
||||
...github.context.repo
|
||||
});
|
||||
if (tree.data.truncated) {
|
||||
truncated = true;
|
||||
tree = await octokit.git.getTree({
|
||||
tree_sha: sha,
|
||||
...github.context.repo
|
||||
});
|
||||
}
|
||||
const result = [];
|
||||
for (const tr of tree.data.tree) {
|
||||
const file = `${path}${tr.path}`;
|
||||
if (tr.type === 'tree') {
|
||||
if (tr.type === 'blob') {
|
||||
result.push(file);
|
||||
}
|
||||
else if (tr.type === 'tree' && truncated) {
|
||||
const files = await listGitTree(octokit, tr.sha, `${file}/`);
|
||||
result.push(...files);
|
||||
}
|
||||
else {
|
||||
result.push(file);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
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
Loading…
Add table
Add a link
Reference in a new issue