Cleanup createCheck
parent
4a83d63c34
commit
0e1590dc44
File diff suppressed because one or more lines are too long
|
|
@ -40,8 +40,8 @@ async function action() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createCheck) {
|
if (createCheck) {
|
||||||
const fail = await ResultsCheck.publishResults(artifactsPath, checkName, githubToken);
|
const failedTestCount = await ResultsCheck.createCheck(artifactsPath, checkName, githubToken);
|
||||||
if (fail > 0) {
|
if (failedTestCount >= 1) {
|
||||||
core.setFailed(`Tests Failed! Check '${checkName}' for details.`);
|
core.setFailed(`Tests Failed! Check '${checkName}' for details.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
import * as core from '@actions/core';
|
|
||||||
import { RunMeta, TestMeta } from './ts/meta.ts';
|
|
||||||
|
|
||||||
class ReportConverter {
|
|
||||||
static convertReport(filename, report) {
|
|
||||||
core.info(`Start analyzing report: ${filename}`);
|
|
||||||
core.debug(JSON.stringify(report));
|
|
||||||
const run = report['test-run'];
|
|
||||||
const meta = new RunMeta(filename);
|
|
||||||
|
|
||||||
meta.total = Number(run._attributes.total);
|
|
||||||
meta.failed = Number(run._attributes.failed);
|
|
||||||
meta.skipped = Number(run._attributes.skipped);
|
|
||||||
meta.passed = Number(run._attributes.passed);
|
|
||||||
meta.duration = Number(run._attributes.duration);
|
|
||||||
|
|
||||||
core.debug(`test-suite length ${run['test-suite'].length} and value ${run['test-suite']}`);
|
|
||||||
meta.addTests(ReportConverter.convertSuite(run['test-suite']));
|
|
||||||
core.debug(`meta length ${meta.suites.length}`);
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
static convertSuite(suites) {
|
|
||||||
if (Array.isArray(suites)) {
|
|
||||||
const innerResult = [];
|
|
||||||
suites.forEach(suite => {
|
|
||||||
innerResult.push(ReportConverter.convertSuite(suite));
|
|
||||||
});
|
|
||||||
core.debug(`suite innerResult length ${innerResult.length}`);
|
|
||||||
return innerResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.debug(`Analyze suite ${suites._attributes.type} / ${suites._attributes.fullname}`);
|
|
||||||
const result = [];
|
|
||||||
const innerSuite = suites['test-suite'];
|
|
||||||
if (innerSuite) {
|
|
||||||
core.debug(`pushing suite ${innerSuite._attributes.id}`);
|
|
||||||
result.push(...ReportConverter.convertSuite(innerSuite));
|
|
||||||
core.debug(`suite ${innerSuite._attributes.id} pushed result to length ${result.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = suites['test-case'];
|
|
||||||
if (tests) {
|
|
||||||
core.debug(`tests length ${tests.length}`);
|
|
||||||
result.push(...ReportConverter.convertTests(suites._attributes.fullname, tests));
|
|
||||||
core.debug(`tests pushed result to length ${result.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
core.debug(`suite result length ${result.length}`);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static convertTests(suite, tests) {
|
|
||||||
if (Array.isArray(tests)) {
|
|
||||||
const result = [];
|
|
||||||
tests.forEach(test => {
|
|
||||||
core.debug(`pushing test ${test._attributes.name}`);
|
|
||||||
result.push(ReportConverter.convertTestCase(suite, test));
|
|
||||||
core.debug(`test ${test._attributes.name} pushed result to length ${result.length}`);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ReportConverter.convertTestCase(suite, tests)];
|
|
||||||
}
|
|
||||||
|
|
||||||
static convertTestCase(suite, testCase) {
|
|
||||||
const { name, fullname, result, failure, duration } = testCase._attributes;
|
|
||||||
const meta = new TestMeta(suite, name);
|
|
||||||
meta.result = result;
|
|
||||||
meta.duration = Number(duration);
|
|
||||||
|
|
||||||
if (!failure) {
|
|
||||||
core.debug(`Skip test ${fullname} without failure data`);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.debug(`Convert data for test ${fullname}`);
|
|
||||||
if (failure['stack-trace'] === undefined) {
|
|
||||||
core.warning(`No stack trace for test case: ${fullname}`);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trace = failure['stack-trace'].cdata;
|
|
||||||
const point = ReportConverter.findAnnotationPoint(trace);
|
|
||||||
if (point === undefined) {
|
|
||||||
core.warning('Not able to find entry point for failed test! Test trace:');
|
|
||||||
core.warning(trace);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
meta.annotation = {
|
|
||||||
path: point.path,
|
|
||||||
start_line: point.line,
|
|
||||||
end_line: point.line,
|
|
||||||
annotation_level: 'failure',
|
|
||||||
title: fullname,
|
|
||||||
message: failure.message._cdata,
|
|
||||||
raw_details: trace,
|
|
||||||
};
|
|
||||||
core.info(`- ${meta.annotation.path}:${meta.annotation.start_line} - ${meta.annotation.title}`);
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
static findAnnotationPoint(trace) {
|
|
||||||
const match = trace.match(/at .* in ((?<path>[^:]+):(?<line>\d+))/);
|
|
||||||
return {
|
|
||||||
path: match.groups.path,
|
|
||||||
line: Number(match.groups.line),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ReportConverter;
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as github from '@actions/github';
|
import * as github from '@actions/github';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as xmljs from 'xml-js';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
import ReportConverter from './report-converter';
|
import ResultsParser from './results-parser';
|
||||||
import { RunMeta } from './ts/meta.ts';
|
import { RunMeta } from './ts/meta.ts';
|
||||||
|
|
||||||
class ResultsCheck {
|
class ResultsCheck {
|
||||||
static async publishResults(artifactsPath, checkName, githubToken) {
|
static async createCheck(artifactsPath, checkName, githubToken) {
|
||||||
// Parse all reports
|
// Parse all results files
|
||||||
const runs = [];
|
const runs = [];
|
||||||
const files = fs.readdirSync(artifactsPath);
|
const files = fs.readdirSync(artifactsPath);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async filepath => {
|
files.map(async filepath => {
|
||||||
if (!filepath.endsWith('.xml')) return;
|
if (!filepath.endsWith('.xml')) return;
|
||||||
core.info(`Processing file ${filepath}...`);
|
core.info(`Processing file ${filepath}...`);
|
||||||
const fileData = await ResultsCheck.parseReport(path.join(artifactsPath, filepath));
|
const fileData = await ResultsParser.parseResults(path.join(artifactsPath, filepath));
|
||||||
core.info(fileData.summary);
|
core.info(fileData.summary);
|
||||||
runs.push(fileData);
|
runs.push(fileData);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prepare run summary
|
// Combine all results into a single run summary
|
||||||
const runSummary = new RunMeta('Test Results');
|
const runSummary = new RunMeta('Test Results');
|
||||||
runs.forEach(run => {
|
runs.forEach(run => {
|
||||||
runSummary.total += run.total;
|
runSummary.total += run.total;
|
||||||
|
|
@ -35,38 +34,24 @@ class ResultsCheck {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log run summary
|
// Log
|
||||||
core.info('=================');
|
core.info('=================');
|
||||||
core.info('Analyze result:');
|
core.info('Analyze result:');
|
||||||
core.info(runSummary.summary);
|
core.info(runSummary.summary);
|
||||||
|
|
||||||
// Create check
|
// Call GitHub API
|
||||||
await ResultsCheck.createCheck(
|
await ResultsCheck.requestGitHubCheck(checkName, githubToken, runs, runSummary);
|
||||||
checkName,
|
|
||||||
githubToken,
|
|
||||||
runs,
|
|
||||||
runSummary,
|
|
||||||
runSummary.extractAnnotations(),
|
|
||||||
);
|
|
||||||
return runSummary.failed;
|
return runSummary.failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async parseReport(filepath) {
|
static async requestGitHubCheck(checkName, githubToken, runs, runSummary) {
|
||||||
core.info(`Trying to open ${filepath}`);
|
|
||||||
const file = await fs.promises.readFile(filepath, 'utf8');
|
|
||||||
const report = xmljs.xml2js(file, { compact: true });
|
|
||||||
core.info(`File ${filepath} parsed...`);
|
|
||||||
|
|
||||||
return ReportConverter.convertReport(path.basename(filepath), report);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async createCheck(checkName, githubToken, runs, runSummary, annotations) {
|
|
||||||
const pullRequest = github.context.payload.pull_request;
|
const pullRequest = github.context.payload.pull_request;
|
||||||
const headSha = (pullRequest && pullRequest.head.sha) || github.context.sha;
|
const headSha = (pullRequest && pullRequest.head.sha) || github.context.sha;
|
||||||
|
|
||||||
const summary = await ResultsCheck.renderSummary(runs);
|
|
||||||
const text = await ResultsCheck.renderText(runs);
|
|
||||||
const title = runSummary.summary;
|
const title = runSummary.summary;
|
||||||
|
const summary = await ResultsCheck.renderSummary(runs);
|
||||||
|
const details = await ResultsCheck.renderDetails(runs);
|
||||||
|
const annotations = runSummary.extractAnnotations();
|
||||||
|
|
||||||
core.info(`Posting results for ${headSha}`);
|
core.info(`Posting results for ${headSha}`);
|
||||||
const createCheckRequest = {
|
const createCheckRequest = {
|
||||||
|
|
@ -78,7 +63,7 @@ class ResultsCheck {
|
||||||
output: {
|
output: {
|
||||||
title,
|
title,
|
||||||
summary,
|
summary,
|
||||||
text,
|
text: details,
|
||||||
annotations: annotations.slice(0, 50),
|
annotations: annotations.slice(0, 50),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -91,13 +76,18 @@ class ResultsCheck {
|
||||||
return ResultsCheck.render(`${__dirname}/../views/summary.hbs`, runMetas);
|
return ResultsCheck.render(`${__dirname}/../views/summary.hbs`, runMetas);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async renderText(runMetas) {
|
static async renderDetails(runMetas) {
|
||||||
return ResultsCheck.render(`${__dirname}/../views/text.hbs`, runMetas);
|
return ResultsCheck.render(`${__dirname}/../views/details.hbs`, runMetas);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async render(viewPath, runMetas) {
|
static async render(viewPath, runMetas) {
|
||||||
|
Handlebars.registerHelper('indent', toIndent =>
|
||||||
|
toIndent
|
||||||
|
.split('\n')
|
||||||
|
.map(s => ` ${s}`)
|
||||||
|
.join('\n'),
|
||||||
|
);
|
||||||
const source = await fs.promises.readFile(viewPath, 'utf8');
|
const source = await fs.promises.readFile(viewPath, 'utf8');
|
||||||
Handlebars.registerHelper('indent', ResultsCheck.indentHelper);
|
|
||||||
const template = Handlebars.compile(source);
|
const template = Handlebars.compile(source);
|
||||||
return template(
|
return template(
|
||||||
{ runs: runMetas },
|
{ runs: runMetas },
|
||||||
|
|
@ -107,13 +97,6 @@ class ResultsCheck {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static indentHelper(argument) {
|
|
||||||
return argument
|
|
||||||
.split('\n')
|
|
||||||
.map(s => ` ${s}`)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ResultsCheck;
|
export default ResultsCheck;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import * as xmljs from 'xml-js';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { RunMeta, TestMeta } from './ts/meta.ts';
|
||||||
|
|
||||||
|
class ResultsParser {
|
||||||
|
static async parseResults(filepath) {
|
||||||
|
core.info(`Trying to open ${filepath}`);
|
||||||
|
const file = await fs.promises.readFile(filepath, 'utf8');
|
||||||
|
const results = xmljs.xml2js(file, { compact: true });
|
||||||
|
core.info(`File ${filepath} parsed...`);
|
||||||
|
|
||||||
|
return ResultsParser.convertResults(path.basename(filepath), results);
|
||||||
|
}
|
||||||
|
|
||||||
|
static convertResults(filename, filedata) {
|
||||||
|
core.info(`Start analyzing results: ${filename}`);
|
||||||
|
|
||||||
|
const run = filedata['test-run'];
|
||||||
|
const runMeta = new RunMeta(filename);
|
||||||
|
|
||||||
|
runMeta.total = Number(run._attributes.total);
|
||||||
|
runMeta.failed = Number(run._attributes.failed);
|
||||||
|
runMeta.skipped = Number(run._attributes.skipped);
|
||||||
|
runMeta.passed = Number(run._attributes.passed);
|
||||||
|
runMeta.duration = Number(run._attributes.duration);
|
||||||
|
runMeta.addTests(ResultsParser.convertSuite(run['test-suite']));
|
||||||
|
|
||||||
|
return runMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static convertSuite(suites) {
|
||||||
|
if (Array.isArray(suites)) {
|
||||||
|
const innerResult = [];
|
||||||
|
suites.forEach(suite => {
|
||||||
|
innerResult.push(ResultsParser.convertSuite(suite));
|
||||||
|
});
|
||||||
|
return innerResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
const innerSuite = suites['test-suite'];
|
||||||
|
if (innerSuite) {
|
||||||
|
result.push(...ResultsParser.convertSuite(innerSuite));
|
||||||
|
}
|
||||||
|
|
||||||
|
const tests = suites['test-case'];
|
||||||
|
if (tests) {
|
||||||
|
result.push(...ResultsParser.convertTests(suites._attributes.fullname, tests));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static convertTests(suite, tests) {
|
||||||
|
if (Array.isArray(tests)) {
|
||||||
|
const result = [];
|
||||||
|
tests.forEach(test => {
|
||||||
|
result.push(ResultsParser.convertTestCase(suite, test));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ResultsParser.convertTestCase(suite, tests)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static convertTestCase(suite, testCase) {
|
||||||
|
const { name, fullname, result, failure, duration } = testCase._attributes;
|
||||||
|
const testMeta = new TestMeta(suite, name);
|
||||||
|
testMeta.result = result;
|
||||||
|
testMeta.duration = Number(duration);
|
||||||
|
|
||||||
|
if (!failure) {
|
||||||
|
core.debug(`Skip test ${fullname} without failure data`);
|
||||||
|
return testMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.debug(`Convert data for test ${fullname}`);
|
||||||
|
if (failure['stack-trace'] === undefined) {
|
||||||
|
core.warning(`No stack trace for test case: ${fullname}`);
|
||||||
|
return testMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trace = failure['stack-trace'].cdata;
|
||||||
|
const point = ResultsParser.findAnnotationPoint(trace);
|
||||||
|
if (point === undefined) {
|
||||||
|
core.warning('Not able to find entry point for failed test! Test trace:');
|
||||||
|
core.warning(trace);
|
||||||
|
return testMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
testMeta.annotation = {
|
||||||
|
path: point.path,
|
||||||
|
start_line: point.line,
|
||||||
|
end_line: point.line,
|
||||||
|
annotation_level: 'failure',
|
||||||
|
title: fullname,
|
||||||
|
message: failure.message._cdata,
|
||||||
|
raw_details: trace,
|
||||||
|
};
|
||||||
|
core.info(
|
||||||
|
`- ${testMeta.annotation.path}:${testMeta.annotation.start_line} - ${testMeta.annotation.title}`,
|
||||||
|
);
|
||||||
|
return testMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static findAnnotationPoint(trace) {
|
||||||
|
const match = trace.match(/at .* in ((?<path>[^:]+):(?<line>\d+))/);
|
||||||
|
return {
|
||||||
|
path: match.groups.path,
|
||||||
|
line: Number(match.groups.line),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResultsParser;
|
||||||
Loading…
Reference in New Issue