This commit is contained in:
A. J. Kaptijn 2024-01-17 10:02:10 +01:00
parent 45ad0f6ed6
commit 26b8b7c891
2 changed files with 816 additions and 13 deletions

803
dist/index.js generated vendored
View file

@ -131,9 +131,14 @@ const github = __importStar(__nccwpck_require__(5438));
const local_file_provider_1 = __nccwpck_require__(9399); const local_file_provider_1 = __nccwpck_require__(9399);
const get_annotations_1 = __nccwpck_require__(5867); const get_annotations_1 = __nccwpck_require__(5867);
const get_report_1 = __nccwpck_require__(3737); const get_report_1 = __nccwpck_require__(3737);
const dart_json_parser_1 = __nccwpck_require__(4528);
const dotnet_trx_parser_1 = __nccwpck_require__(2664); 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); 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 webhook_1 = __nccwpck_require__(1095); const webhook_1 = __nccwpck_require__(1095);
const fs_1 = __importDefault(__nccwpck_require__(7147)); const fs_1 = __importDefault(__nccwpck_require__(7147));
const bent_1 = __importDefault(__nccwpck_require__(3113)); const bent_1 = __importDefault(__nccwpck_require__(3113));
@ -268,6 +273,7 @@ class TestReporter {
const results = []; const results = [];
for (const { file, content } of files) { for (const { file, content } of files) {
try { try {
core.info(`Processing test results from ${file}`);
const tr = yield parser.parse(file, content); const tr = yield parser.parse(file, content);
results.push(tr); results.push(tr);
} }
@ -287,15 +293,12 @@ class TestReporter {
const summary = (0, get_report_1.getReport)(results, { listSuites, listTests, baseUrl, onlySummary }); const summary = (0, get_report_1.getReport)(results, { listSuites, listTests, baseUrl, onlySummary });
core.info('Creating annotations'); core.info('Creating annotations');
const annotations = (0, get_annotations_1.getAnnotations)(results, this.maxAnnotations); const annotations = (0, get_annotations_1.getAnnotations)(results, this.maxAnnotations);
const isFailed = this.failOnError && results.some(tr => tr.result === 'failed'); const isFailed = results.some(tr => tr.result === 'failed');
const conclusion = isFailed ? 'failure' : 'success'; const conclusion = isFailed ? 'failure' : 'success';
const passed = results.reduce((sum, tr) => sum + tr.passed, 0); const icon = isFailed ? markdown_utils_1.Icon.fail : markdown_utils_1.Icon.success;
const failed = results.reduce((sum, tr) => sum + tr.failed, 0);
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0);
const shortSummary = `${passed} passed, ${failed} failed and ${skipped} skipped `;
core.info(`Updating check run conclusion (${conclusion}) and output`); core.info(`Updating check run conclusion (${conclusion}) and output`);
const resp = yield this.octokit.rest.checks.update(Object.assign({ check_run_id: createResp.data.id, conclusion, status: 'completed', output: { const resp = yield this.octokit.rest.checks.update(Object.assign({ check_run_id: createResp.data.id, conclusion, status: 'completed', output: {
title: shortSummary, title: `${name} ${icon}`,
summary, summary,
annotations annotations
} }, github.context.repo)); } }, github.context.repo));
@ -340,8 +343,18 @@ class TestReporter {
} }
getParser(reporter, options) { getParser(reporter, options) {
switch (reporter) { switch (reporter) {
case 'dart-json':
return new dart_json_parser_1.DartJsonParser(options, 'dart');
case 'dotnet-trx': case 'dotnet-trx':
return new dotnet_trx_parser_1.DotnetTrxParser(options); 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':
return new mocha_json_parser_1.MochaJsonParser(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}'`);
} }
@ -350,6 +363,279 @@ class TestReporter {
main(); main();
/***/ }),
/***/ 4528:
/***/ (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.DartJsonParser = void 0;
const path_utils_1 = __nccwpck_require__(4070);
const dart_json_types_1 = __nccwpck_require__(7887);
const test_results_1 = __nccwpck_require__(2768);
class TestRun {
constructor(path, suites, success, time) {
this.path = path;
this.suites = suites;
this.success = success;
this.time = time;
}
}
class TestSuite {
constructor(suite) {
this.suite = suite;
this.groups = {};
}
}
class TestGroup {
constructor(group) {
this.group = group;
this.tests = [];
}
}
class TestCase {
constructor(testStart) {
this.testStart = testStart;
this.print = [];
this.groupId = testStart.test.groupIDs[testStart.test.groupIDs.length - 1];
}
get result() {
var _a, _b, _c, _d;
if ((_a = this.testDone) === null || _a === void 0 ? void 0 : _a.skipped) {
return 'skipped';
}
if (((_b = this.testDone) === null || _b === void 0 ? void 0 : _b.result) === 'success') {
return 'success';
}
if (((_c = this.testDone) === null || _c === void 0 ? void 0 : _c.result) === 'error' || ((_d = this.testDone) === null || _d === void 0 ? void 0 : _d.result) === 'failure') {
return 'failed';
}
return undefined;
}
get time() {
return this.testDone !== undefined ? this.testDone.time - this.testStart.time : 0;
}
}
class DartJsonParser {
constructor(options, sdk) {
this.options = options;
this.sdk = sdk;
}
parse(path, content) {
return __awaiter(this, void 0, void 0, function* () {
const tr = this.getTestRun(path, content);
const result = this.getTestRunResult(tr);
return Promise.resolve(result);
});
}
getTestRun(path, content) {
const lines = content.split(/\n\r?/g);
const events = lines
.map((str, i) => {
if (str.trim() === '') {
return null;
}
try {
return JSON.parse(str);
}
catch (e) {
const errWithCol = e;
const col = errWithCol.columnNumber !== undefined ? `:${errWithCol.columnNumber}` : '';
throw new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`);
}
})
.filter(evt => evt != null);
let success = false;
let totalTime = 0;
const suites = {};
const tests = {};
for (const evt of events) {
if ((0, dart_json_types_1.isSuiteEvent)(evt)) {
suites[evt.suite.id] = new TestSuite(evt.suite);
}
else if ((0, dart_json_types_1.isGroupEvent)(evt)) {
suites[evt.group.suiteID].groups[evt.group.id] = new TestGroup(evt.group);
}
else if ((0, dart_json_types_1.isTestStartEvent)(evt) && evt.test.url !== null) {
const test = new TestCase(evt);
const suite = suites[evt.test.suiteID];
const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]];
group.tests.push(test);
tests[evt.test.id] = test;
}
else if ((0, dart_json_types_1.isTestDoneEvent)(evt) && !evt.hidden && tests[evt.testID]) {
tests[evt.testID].testDone = evt;
}
else if ((0, dart_json_types_1.isErrorEvent)(evt) && tests[evt.testID]) {
tests[evt.testID].error = evt;
}
else if ((0, dart_json_types_1.isMessageEvent)(evt) && tests[evt.testID]) {
tests[evt.testID].print.push(evt);
}
else if ((0, dart_json_types_1.isDoneEvent)(evt)) {
success = evt.success;
totalTime = evt.time;
}
}
return new TestRun(path, Object.values(suites), success, totalTime);
}
getTestRunResult(tr) {
const suites = tr.suites.map(s => {
return new test_results_1.TestSuiteResult(this.getRelativePath(s.suite.path), this.getGroups(s));
});
return new test_results_1.TestRunResult(tr.path, suites, tr.time);
}
getGroups(suite) {
const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0);
groups.sort((a, b) => { var _a, _b; return ((_a = a.group.line) !== null && _a !== void 0 ? _a : 0) - ((_b = b.group.line) !== null && _b !== void 0 ? _b : 0); });
return groups.map(group => {
group.tests.sort((a, b) => { var _a, _b; return ((_a = a.testStart.test.line) !== null && _a !== void 0 ? _a : 0) - ((_b = b.testStart.test.line) !== null && _b !== void 0 ? _b : 0); });
const tests = group.tests.map(tc => {
const error = this.getError(suite, tc);
const testName = group.group.name !== undefined && tc.testStart.test.name.startsWith(group.group.name)
? tc.testStart.test.name.slice(group.group.name.length).trim()
: tc.testStart.test.name.trim();
return new test_results_1.TestCaseResult(testName, tc.result, tc.time, error);
});
return new test_results_1.TestGroupResult(group.group.name, tests);
});
}
getError(testSuite, test) {
var _a, _b, _c, _d, _e, _f;
if (!this.options.parseErrors || !test.error) {
return undefined;
}
const { trackedFiles } = this.options;
const stackTrace = (_b = (_a = test.error) === null || _a === void 0 ? void 0 : _a.stackTrace) !== null && _b !== void 0 ? _b : '';
const print = test.print
.filter(p => p.messageType === 'print')
.map(p => p.message)
.join('\n');
const details = [print, stackTrace].filter(str => str !== '').join('\n');
const src = this.exceptionThrowSource(details, trackedFiles);
const message = this.getErrorMessage((_d = (_c = test.error) === null || _c === void 0 ? void 0 : _c.error) !== null && _d !== void 0 ? _d : '', print);
let path;
let line;
if (src !== undefined) {
path = src.path;
line = src.line;
}
else {
const testStartPath = this.getRelativePath(testSuite.suite.path);
if (trackedFiles.includes(testStartPath)) {
path = testStartPath;
line = (_f = (_e = test.testStart.test.root_line) !== null && _e !== void 0 ? _e : test.testStart.test.line) !== null && _f !== void 0 ? _f : undefined;
}
}
return {
path,
line,
message,
details
};
}
getErrorMessage(message, print) {
if (this.sdk === 'flutter') {
const uselessMessageRe = /^Test failed\. See exception logs above\.\nThe test description was:/m;
const flutterPrintRe = /^ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK +\s+(.*)\s+When the exception was thrown, this was the stack:/ms;
if (uselessMessageRe.test(message)) {
const match = print.match(flutterPrintRe);
if (match !== null) {
return match[1];
}
}
}
return message || print;
}
exceptionThrowSource(ex, trackedFiles) {
const lines = ex.split(/\r?\n/g);
// regexp to extract file path and line number from stack trace
const dartRe = /^(?!package:)(.*)\s+(\d+):\d+\s+/;
const flutterRe = /^#\d+\s+.*\((?!package:)(.*):(\d+):\d+\)$/;
const re = this.sdk === 'dart' ? dartRe : flutterRe;
for (const str of lines) {
const match = str.match(re);
if (match !== null) {
const [_, pathStr, lineStr] = match;
const path = (0, path_utils_1.normalizeFilePath)(this.getRelativePath(pathStr));
if (trackedFiles.includes(path)) {
const line = parseInt(lineStr);
return { path, line };
}
}
}
}
getRelativePath(path) {
const prefix = 'file://';
if (path.startsWith(prefix)) {
path = path.substr(prefix.length);
}
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.DartJsonParser = DartJsonParser;
/***/ }),
/***/ 7887:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
/// reflects documentation at https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isMessageEvent = exports.isDoneEvent = exports.isErrorEvent = exports.isTestDoneEvent = exports.isTestStartEvent = exports.isGroupEvent = exports.isSuiteEvent = void 0;
function isSuiteEvent(event) {
return event.type === 'suite';
}
exports.isSuiteEvent = isSuiteEvent;
function isGroupEvent(event) {
return event.type === 'group';
}
exports.isGroupEvent = isGroupEvent;
function isTestStartEvent(event) {
return event.type === 'testStart';
}
exports.isTestStartEvent = isTestStartEvent;
function isTestDoneEvent(event) {
return event.type === 'testDone';
}
exports.isTestDoneEvent = isTestDoneEvent;
function isErrorEvent(event) {
return event.type === 'error';
}
exports.isErrorEvent = isErrorEvent;
function isDoneEvent(event) {
return event.type === 'done';
}
exports.isDoneEvent = isDoneEvent;
function isMessageEvent(event) {
return event.type === 'print';
}
exports.isMessageEvent = isMessageEvent;
/***/ }), /***/ }),
/***/ 2664: /***/ 2664:
@ -531,6 +817,511 @@ class DotnetTrxParser {
exports.DotnetTrxParser = 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;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (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;
};
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.JavaJunitParser = void 0;
const path = __importStar(__nccwpck_require__(1017));
const xml2js_1 = __nccwpck_require__(6189);
const java_stack_trace_element_parser_1 = __nccwpck_require__(5775);
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((0, path_utils_1.normalizeFilePath)(filePath));
}
}
parse(filePath, content) {
return __awaiter(this, void 0, void 0, function* () {
const reportOrSuite = yield 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);
});
}
getJunitReport(filePath, 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 ${filePath}\n\n${e}`);
}
});
}
getTestRunResult(filePath, junit) {
var _a;
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 seconds = parseFloat((_a = junit.testsuites.$) === null || _a === void 0 ? void 0 : _a.time);
const time = isNaN(seconds) ? undefined : seconds * 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 || test.error)
return 'failed';
if (test.skipped)
return 'skipped';
return 'success';
}
getTestCaseError(tc) {
var _a;
if (!this.options.parseErrors) {
return undefined;
}
// We process <error> and <failure> the same way
const failures = (_a = tc.failure) !== null && _a !== void 0 ? _a : tc.error;
if (!failures) {
return undefined;
}
const failure = failures[0];
const details = typeof failure === 'object' ? failure._ : failure;
let filePath;
let line;
if (details != null) {
const src = this.exceptionThrowSource(details);
if (src) {
filePath = src.filePath;
line = src.line;
}
}
return {
path: filePath,
line,
details,
message: typeof failure === 'object' ? failure.message : undefined
};
}
exceptionThrowSource(stackTrace) {
const lines = stackTrace.split(/\r?\n/);
for (const str of lines) {
const stackTraceElement = (0, java_stack_trace_element_parser_1.parseStackTraceElement)(str);
if (stackTraceElement) {
const { tracePath, fileName, lineStr } = stackTraceElement;
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;
/***/ }),
/***/ 5775:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.parseStackTraceElement = void 0;
// classloader and module name are optional:
// at <CLASSLOADER>/<MODULE_NAME_AND_VERSION>/<FULLY_QUALIFIED_METHOD_NAME>(<FILE_NAME>:<LINE_NUMBER>)
// https://github.com/eclipse-openj9/openj9/issues/11452#issuecomment-754946992
const re = /^\s*at (\S+\/\S*\/)?(.*)\((.*):(\d+)\)$/;
function parseStackTraceElement(stackTraceLine) {
const match = stackTraceLine.match(re);
if (match !== null) {
const [_, maybeClassLoaderAndModuleNameAndVersion, tracePath, fileName, lineStr] = match;
const { classLoader, moduleNameAndVersion } = parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion);
return {
classLoader,
moduleNameAndVersion,
tracePath,
fileName,
lineStr
};
}
return undefined;
}
exports.parseStackTraceElement = parseStackTraceElement;
function parseClassLoaderAndModule(maybeClassLoaderAndModuleNameAndVersion) {
if (maybeClassLoaderAndModuleNameAndVersion) {
const res = maybeClassLoaderAndModuleNameAndVersion.split('/');
const classLoader = res[0];
let moduleNameAndVersion = res[1];
if (moduleNameAndVersion === '') {
moduleNameAndVersion = undefined;
}
return { classLoader, moduleNameAndVersion };
}
return { classLoader: undefined, moduleNameAndVersion: undefined };
}
/***/ }),
/***/ 1113:
/***/ (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.JestJunitParser = 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 JestJunitParser {
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 = this.escapeCharacters(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) {
if (!suite.testcase) {
return [];
}
const groups = [];
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)));
}
escapeCharacters(s) {
return s.replace(/([<>])/g, '\\$1');
}
}
exports.JestJunitParser = JestJunitParser;
/***/ }),
/***/ 6043:
/***/ (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.MochaJsonParser = void 0;
const test_results_1 = __nccwpck_require__(2768);
const node_utils_1 = __nccwpck_require__(5824);
const path_utils_1 = __nccwpck_require__(4070);
class MochaJsonParser {
constructor(options) {
this.options = options;
}
parse(path, content) {
return __awaiter(this, void 0, void 0, function* () {
const mocha = this.getMochaJson(path, content);
const result = this.getTestRunResult(path, mocha);
result.sort(true);
return Promise.resolve(result);
});
}
getMochaJson(path, content) {
try {
return JSON.parse(content);
}
catch (e) {
throw new Error(`Invalid JSON at ${path}\n\n${e}`);
}
}
getTestRunResult(resultsPath, mocha) {
const suitesMap = {};
const getSuite = (test) => {
var _a;
const path = this.getRelativePath(test.file);
return (_a = suitesMap[path]) !== null && _a !== void 0 ? _a : (suitesMap[path] = new test_results_1.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 test_results_1.TestRunResult(resultsPath, suites, mocha.stats.duration);
}
processTest(suite, test, result) {
var _a;
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 test_results_1.TestGroupResult(groupName, []);
suite.groups.push(group);
}
const error = this.getTestCaseError(test);
const testCase = new test_results_1.TestCaseResult(test.title, result, (_a = test.duration) !== null && _a !== void 0 ? _a : 0, error);
group.tests.push(testCase);
}
getTestCaseError(test) {
const details = test.err.stack;
const message = test.err.message;
if (details === undefined) {
return undefined;
}
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,
message,
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.MochaJsonParser = MochaJsonParser;
/***/ }), /***/ }),
/***/ 5867: /***/ 5867:

View file

@ -9,10 +9,15 @@ import {TestRunResult} from './test-results'
import {getAnnotations} from './report/get-annotations' import {getAnnotations} from './report/get-annotations'
import {getReport} from './report/get-report' 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 {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser'
import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-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'
import {Icon} from './utils/markdown-utils'
import {IncomingWebhook} from '@slack/webhook' import {IncomingWebhook} from '@slack/webhook'
import fs from 'fs' import fs from 'fs'
import bent from 'bent' import bent from 'bent'
@ -169,6 +174,7 @@ class TestReporter {
const results: TestRunResult[] = [] const results: TestRunResult[] = []
for (const {file, content} of files) { for (const {file, content} of files) {
try { try {
core.info(`Processing test results from ${file}`)
const tr = await parser.parse(file, content) const tr = await parser.parse(file, content)
results.push(tr) results.push(tr)
} catch (error) { } catch (error) {
@ -197,13 +203,9 @@ class TestReporter {
core.info('Creating annotations') core.info('Creating annotations')
const annotations = getAnnotations(results, this.maxAnnotations) const annotations = getAnnotations(results, this.maxAnnotations)
const isFailed = this.failOnError && results.some(tr => tr.result === 'failed') const isFailed = results.some(tr => tr.result === 'failed')
const conclusion = isFailed ? 'failure' : 'success' const conclusion = isFailed ? 'failure' : 'success'
const icon = isFailed ? Icon.fail : Icon.success
const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0)
const shortSummary = `${passed} passed, ${failed} failed and ${skipped} skipped `
core.info(`Updating check run conclusion (${conclusion}) and output`) core.info(`Updating check run conclusion (${conclusion}) and output`)
const resp = await this.octokit.rest.checks.update({ const resp = await this.octokit.rest.checks.update({
@ -211,7 +213,7 @@ class TestReporter {
conclusion, conclusion,
status: 'completed', status: 'completed',
output: { output: {
title: shortSummary, title: `${name} ${icon}`,
summary, summary,
annotations annotations
}, },
@ -263,8 +265,18 @@ class TestReporter {
getParser(reporter: string, options: ParseOptions): TestParser { getParser(reporter: string, options: ParseOptions): TestParser {
switch (reporter) { switch (reporter) {
case 'dart-json':
return new DartJsonParser(options, 'dart')
case 'dotnet-trx': case 'dotnet-trx':
return new DotnetTrxParser(options) return new DotnetTrxParser(options)
case 'flutter-json':
return new DartJsonParser(options, 'flutter')
case 'java-junit':
return new JavaJunitParser(options)
case 'jest-junit':
return new JestJunitParser(options)
case 'mocha-json':
return new MochaJsonParser(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}'`)
} }