Improve logging and error handling

This commit is contained in:
Michal Dorner 2021-01-18 22:21:19 +01:00
parent 967dbab3c6
commit 1ab5efa052
No known key found for this signature in database
GPG key ID: 9EEE04B48DA36786
7 changed files with 174 additions and 33 deletions

142
dist/index.js generated vendored
View file

@ -75,11 +75,13 @@ async function main() {
const parser = getParser(reporter); const parser = getParser(reporter);
const files = await getFiles(path); const files = await getFiles(path);
if (files.length === 0) { if (files.length === 0) {
core.setFailed(`No file matches path ${path}`); core.setFailed(`No file matches path '${path}'`);
return; return;
} }
core.info(`Using test report parser '${reporter}'`);
const result = await parser(files, opts); const result = await parser(files, opts);
const conclusion = result.success ? 'success' : 'failure'; const conclusion = result.success ? 'success' : 'failure';
core.info(`Creating check run '${name}' with conclusion '${conclusion}'`);
await octokit.checks.create({ await octokit.checks.create({
head_sha: sha, head_sha: sha,
name, name,
@ -90,7 +92,7 @@ async function main() {
}); });
core.setOutput('conclusion', conclusion); core.setOutput('conclusion', conclusion);
if (failOnError && !result.success) { if (failOnError && !result.success) {
core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}.`); core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}`);
} }
} }
function getParser(reporter) { function getParser(reporter) {
@ -104,12 +106,13 @@ function getParser(reporter) {
case 'jest-junit': case 'jest-junit':
return jest_junit_parser_1.parseJestJunit; return jest_junit_parser_1.parseJestJunit;
default: default:
throw new Error(`Input parameter 'reporter' is set to invalid value '${reporter}'`); throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`);
} }
} }
async function getFiles(pattern) { async function getFiles(pattern) {
const paths = await fast_glob_1.default(pattern, { dot: true }); const paths = await fast_glob_1.default(pattern, { dot: true });
return Promise.all(paths.map(async (path) => { return Promise.all(paths.map(async (path) => {
core.info(`Reading test report '${path}'`);
const content = await fs.promises.readFile(path, { encoding: 'utf8' }); const content = await fs.promises.readFile(path, { encoding: 'utf8' });
return { path, content }; return { path, content };
})); }));
@ -125,11 +128,31 @@ run();
"use strict"; "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;
};
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.parseDartJson = void 0; exports.parseDartJson = void 0;
const core = __importStar(__webpack_require__(2186));
const get_report_1 = __importDefault(__webpack_require__(3737)); const get_report_1 = __importDefault(__webpack_require__(3737));
const file_utils_1 = __webpack_require__(2711); const file_utils_1 = __webpack_require__(2711);
const markdown_utils_1 = __webpack_require__(6482); const markdown_utils_1 = __webpack_require__(6482);
@ -193,8 +216,22 @@ async function parseDartJson(files, options) {
} }
exports.parseDartJson = parseDartJson; exports.parseDartJson = parseDartJson;
function getTestRun(path, content) { function getTestRun(path, content) {
const lines = content.split(/\n\r?/g).filter(line => line !== ''); core.info(`Parsing content of '${path}'`);
const events = lines.map(str => JSON.parse(str)); 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 col = e.columnNumber !== undefined ? `:${e.columnNumber}` : '';
new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`);
}
})
.filter(evt => evt != null);
let success = false; let success = false;
let totalTime = 0; let totalTime = 0;
const suites = {}; const suites = {};
@ -356,11 +393,31 @@ exports.isDoneEvent = isDoneEvent;
"use strict"; "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;
};
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.exceptionThrowSource = exports.parseDotnetTrx = void 0; exports.exceptionThrowSource = exports.parseDotnetTrx = void 0;
const core = __importStar(__webpack_require__(2186));
const xml2js_1 = __webpack_require__(6189); const xml2js_1 = __webpack_require__(6189);
const file_utils_1 = __webpack_require__(2711); const file_utils_1 = __webpack_require__(2711);
const xml_utils_1 = __webpack_require__(8653); const xml_utils_1 = __webpack_require__(8653);
@ -395,7 +452,7 @@ async function parseDotnetTrx(files, options) {
const testRuns = []; const testRuns = [];
const testClasses = []; const testClasses = [];
for (const file of files) { for (const file of files) {
const trx = await getTrxReport(file.content); const trx = await getTrxReport(file);
const tc = getTestClasses(trx); const tc = getTestClasses(trx);
const tr = getTestRunResult(file.path, trx, tc); const tr = getTestRunResult(file.path, trx, tc);
testRuns.push(tr); testRuns.push(tr);
@ -413,10 +470,16 @@ async function parseDotnetTrx(files, options) {
}; };
} }
exports.parseDotnetTrx = parseDotnetTrx; exports.parseDotnetTrx = parseDotnetTrx;
async function getTrxReport(content) { async function getTrxReport(file) {
return (await xml2js_1.parseStringPromise(content, { core.info(`Parsing content of '${file.path}'`);
attrValueProcessors: [xml_utils_1.parseAttribute] try {
})); return (await xml2js_1.parseStringPromise(file.content, {
attrValueProcessors: [xml_utils_1.parseAttribute]
}));
}
catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`);
}
} }
function getTestRunResult(path, trx, testClasses) { function getTestRunResult(path, trx, testClasses) {
const times = trx.TestRun.Times[0].$; const times = trx.TestRun.Times[0].$;
@ -508,11 +571,31 @@ exports.exceptionThrowSource = exceptionThrowSource;
"use strict"; "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;
};
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.exceptionThrowSource = exports.parseJestJunit = void 0; exports.exceptionThrowSource = exports.parseJestJunit = void 0;
const core = __importStar(__webpack_require__(2186));
const xml2js_1 = __webpack_require__(6189); const xml2js_1 = __webpack_require__(6189);
const markdown_utils_1 = __webpack_require__(6482); const markdown_utils_1 = __webpack_require__(6482);
const file_utils_1 = __webpack_require__(2711); const file_utils_1 = __webpack_require__(2711);
@ -523,7 +606,7 @@ async function parseJestJunit(files, options) {
const junit = []; const junit = [];
const testRuns = []; const testRuns = [];
for (const file of files) { for (const file of files) {
const ju = await getJunitReport(file.content); const ju = await getJunitReport(file);
const tr = getTestRunResult(file.path, ju); const tr = getTestRunResult(file.path, ju);
junit.push(ju); junit.push(ju);
testRuns.push(tr); testRuns.push(tr);
@ -540,10 +623,16 @@ async function parseJestJunit(files, options) {
}; };
} }
exports.parseJestJunit = parseJestJunit; exports.parseJestJunit = parseJestJunit;
async function getJunitReport(content) { async function getJunitReport(file) {
return (await xml2js_1.parseStringPromise(content, { core.info(`Parsing content of '${file.path}'`);
attrValueProcessors: [xml_utils_1.parseAttribute] try {
})); return (await xml2js_1.parseStringPromise(file.content, {
attrValueProcessors: [xml_utils_1.parseAttribute]
}));
}
catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`);
}
} }
function getTestRunResult(path, junit) { function getTestRunResult(path, junit) {
const suites = junit.testsuites.testsuite.map(ts => { const suites = junit.testsuites.testsuite.map(ts => {
@ -633,11 +722,31 @@ exports.exceptionThrowSource = exceptionThrowSource;
/***/ }), /***/ }),
/***/ 3737: /***/ 3737:
/***/ ((__unused_webpack_module, exports, __webpack_require__) => { /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
"use strict"; "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 })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__webpack_require__(2186));
const markdown_utils_1 = __webpack_require__(6482); const markdown_utils_1 = __webpack_require__(6482);
const slugger_1 = __webpack_require__(3328); const slugger_1 = __webpack_require__(3328);
function getReport(results) { function getReport(results) {
@ -651,6 +760,7 @@ function getReport(results) {
} }
exports.default = getReport; exports.default = getReport;
function getRunSummary(tr) { function getRunSummary(tr) {
core.info('Generating check run summary');
const time = `${(tr.time / 1000).toFixed(3)}s`; const time = `${(tr.time / 1000).toFixed(3)}s`;
const headingLine1 = `### ${tr.path}`; const headingLine1 = `### ${tr.path}`;
const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.`; const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.`;

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View file

@ -50,13 +50,15 @@ async function main(): Promise<void> {
const files = await getFiles(path) const files = await getFiles(path)
if (files.length === 0) { if (files.length === 0) {
core.setFailed(`No file matches path ${path}`) core.setFailed(`No file matches path '${path}'`)
return return
} }
core.info(`Using test report parser '${reporter}'`)
const result = await parser(files, opts) const result = await parser(files, opts)
const conclusion = result.success ? 'success' : 'failure' const conclusion = result.success ? 'success' : 'failure'
core.info(`Creating check run '${name}' with conclusion '${conclusion}'`)
await octokit.checks.create({ await octokit.checks.create({
head_sha: sha, head_sha: sha,
name, name,
@ -68,7 +70,7 @@ async function main(): Promise<void> {
core.setOutput('conclusion', conclusion) core.setOutput('conclusion', conclusion)
if (failOnError && !result.success) { if (failOnError && !result.success) {
core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}.`) core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}`)
} }
} }
@ -83,7 +85,7 @@ function getParser(reporter: string): ParseTestResult {
case 'jest-junit': case 'jest-junit':
return parseJestJunit return parseJestJunit
default: default:
throw new Error(`Input parameter 'reporter' is set to invalid value '${reporter}'`) throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
} }
} }
@ -91,6 +93,7 @@ export async function getFiles(pattern: string): Promise<FileContent[]> {
const paths = await glob(pattern, {dot: true}) const paths = await glob(pattern, {dot: true})
return Promise.all( return Promise.all(
paths.map(async path => { paths.map(async path => {
core.info(`Reading test report '${path}'`)
const content = await fs.promises.readFile(path, {encoding: 'utf8'}) const content = await fs.promises.readFile(path, {encoding: 'utf8'})
return {path, content} return {path, content}
}) })

View file

@ -1,3 +1,4 @@
import * as core from '@actions/core'
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types' import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
import getReport from '../../report/get-report' import getReport from '../../report/get-report'
@ -85,8 +86,21 @@ export async function parseDartJson(files: FileContent[], options: ParseOptions)
} }
function getTestRun(path: string, content: string): TestRun { function getTestRun(path: string, content: string): TestRun {
const lines = content.split(/\n\r?/g).filter(line => line !== '') core.info(`Parsing content of '${path}'`)
const events = lines.map(str => JSON.parse(str)) as ReportEvent[] 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 col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`)
}
})
.filter(evt => evt != null) as ReportEvent[]
let success = false let success = false
let totalTime = 0 let totalTime = 0

View file

@ -1,3 +1,4 @@
import * as core from '@actions/core'
import {ErrorInfo, Outcome, TestMethod, TrxReport} from './dotnet-trx-types' import {ErrorInfo, Outcome, TestMethod, TrxReport} from './dotnet-trx-types'
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types' import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
@ -29,7 +30,7 @@ class Test {
readonly error?: ErrorInfo readonly error?: ErrorInfo
) {} ) {}
get result(): TestExecutionResult { get result(): TestExecutionResult | undefined {
switch (this.outcome) { switch (this.outcome) {
case 'Passed': case 'Passed':
return 'success' return 'success'
@ -46,7 +47,7 @@ export async function parseDotnetTrx(files: FileContent[], options: ParseOptions
const testClasses: TestClass[] = [] const testClasses: TestClass[] = []
for (const file of files) { for (const file of files) {
const trx = await getTrxReport(file.content) const trx = await getTrxReport(file)
const tc = getTestClasses(trx) const tc = getTestClasses(trx)
const tr = getTestRunResult(file.path, trx, tc) const tr = getTestRunResult(file.path, trx, tc)
testRuns.push(tr) testRuns.push(tr)
@ -66,10 +67,15 @@ export async function parseDotnetTrx(files: FileContent[], options: ParseOptions
} }
} }
async function getTrxReport(content: string): Promise<TrxReport> { async function getTrxReport(file: FileContent): Promise<TrxReport> {
return (await parseStringPromise(content, { core.info(`Parsing content of '${file.path}'`)
attrValueProcessors: [parseAttribute] try {
})) as TrxReport return (await parseStringPromise(file.content, {
attrValueProcessors: [parseAttribute]
})) as TrxReport
} catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
}
} }
function getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult { function getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {

View file

@ -1,3 +1,4 @@
import * as core from '@actions/core'
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types' import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
import {parseStringPromise} from 'xml2js' import {parseStringPromise} from 'xml2js'
@ -20,7 +21,7 @@ export async function parseJestJunit(files: FileContent[], options: ParseOptions
const testRuns: TestRunResult[] = [] const testRuns: TestRunResult[] = []
for (const file of files) { for (const file of files) {
const ju = await getJunitReport(file.content) const ju = await getJunitReport(file)
const tr = getTestRunResult(file.path, ju) const tr = getTestRunResult(file.path, ju)
junit.push(ju) junit.push(ju)
testRuns.push(tr) testRuns.push(tr)
@ -39,10 +40,15 @@ export async function parseJestJunit(files: FileContent[], options: ParseOptions
} }
} }
async function getJunitReport(content: string): Promise<JunitReport> { async function getJunitReport(file: FileContent): Promise<JunitReport> {
return (await parseStringPromise(content, { core.info(`Parsing content of '${file.path}'`)
attrValueProcessors: [parseAttribute] try {
})) as JunitReport return (await parseStringPromise(file.content, {
attrValueProcessors: [parseAttribute]
})) as JunitReport
} catch (e) {
throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
}
} }
function getTestRunResult(path: string, junit: JunitReport): TestRunResult { function getTestRunResult(path: string, junit: JunitReport): TestRunResult {

View file

@ -1,3 +1,4 @@
import * as core from '@actions/core'
import {TestExecutionResult, TestRunResult, TestSuiteResult} from './test-results' import {TestExecutionResult, TestRunResult, TestSuiteResult} from './test-results'
import {Align, Icon, link, table} from '../utils/markdown-utils' import {Align, Icon, link, table} from '../utils/markdown-utils'
import {slug} from '../utils/slugger' import {slug} from '../utils/slugger'
@ -14,6 +15,7 @@ export default function getReport(results: TestRunResult[]): string {
} }
function getRunSummary(tr: TestRunResult): string { function getRunSummary(tr: TestRunResult): string {
core.info('Generating check run summary')
const time = `${(tr.time / 1000).toFixed(3)}s` const time = `${(tr.time / 1000).toFixed(3)}s`
const headingLine1 = `### ${tr.path}` const headingLine1 = `### ${tr.path}`
const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.` const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.`