Skip to content

Commit

Permalink
Release v1.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
GaelGirodon committed May 24, 2024
1 parent 69b5c64 commit e2b7770
Show file tree
Hide file tree
Showing 28 changed files with 274 additions and 193 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ jobs:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm run test:ci
env:
GIST_TOKEN: ${{ secrets.GIST_TOKEN }}
- run: npm run build
- run: sed -i '0,/index.js/s//dist\/index.js/' action.yml package.json
- run: rm -rf test/data
- uses: ./
with:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## 1.5.0 - 2024-05-25

- Report skipped tests

## 1.4.0 - 2024-05-22

- Improve JUnit reports loading
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ advantage of Shields customization features (through the query string).
![tests](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fgist.githubusercontent.com%2FGaelGirodon%2F715c62717519f634185af0ebde234992%2Fraw%2Frepo-go-tests.json)
![tests](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fgist.githubusercontent.com%2FGaelGirodon%2F715c62717519f634185af0ebde234992%2Fraw%2Frepo-junit-tests.json)

This badge displays the number of passed and failed tests extracted from a test
report.
This badge displays the number of passed, failed and skipped tests extracted
from test report(s).

```json
{"schemaVersion":1,"label":"tests","message":"3 passed","color":"brightgreen"}
Expand Down Expand Up @@ -89,7 +89,7 @@ Only matched report formats will get a file uploaded to the Gist.
Write the verbose test output (`>` or `tee`) with coverage enabled to a single
`test*.{out,txt}` file next to the `go.mod` file:

- `RUN`, `PASS` and `FAIL` flags will be used to count tests
- `RUN`, `PASS`, `FAIL` and `SKIP` flags will be used to count tests
- The last percentage will be used as the coverage value

`go tool cover -func=cover.out` output may be appended to the above file to make
Expand Down Expand Up @@ -118,8 +118,8 @@ support this format too, natively or using an additional reporter:
- **Deno**: `deno test --junit-path=report.xml`
- **PHPUnit**: `phpunit --log-junit report.xml`

The number of tests and failures will be extracted from top-level `<testsuite>`
tags, from all matching and valid report files.
The number of tests (total, failed and skipped) will be extracted from
top-level `<testsuite>` tags, from all matching and valid report files.

➡️ `{repo}-[{ref}-]junit-tests.json`

Expand Down
156 changes: 77 additions & 79 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33476,41 +33476,43 @@ async function globNearest(patterns) {


/**
* Load reports using Go tests and coverage formats.
* Load reports using Go test and coverage formats.
* @param {string} root Root search directory
* @returns {Promise<import('./index.js').Report[]>} Go tests and coverage reports
* @returns Go test and coverage reports
*/
async function getReports(root) {
core.info('Load Go tests and coverage report');
core.info('Load Go test and coverage report');
const goMods = await globNearest([(0,external_path_.join)(root, '**/go.mod')]);
if (goMods.length === 0) {
core.info('go.mod file not found, skipping');
return [];
}
const dir = (0,external_path_.dirname)(goMods[0]);
core.info(`Search Go reports in '${dir}'`);
const badges = [];
/** @type {Omit<TestReport | CoverageReport, 'format'>[]} */
const reports = [];
const patterns = ['test*.out', 'test*.txt'].map(t => (0,external_path_.join)(dir, t));
const reports = await globNearest(patterns);
for (const r of reports) {
core.info(`Load Go report '${r}'`);
const report = await external_fs_.promises.readFile(r, { encoding: 'utf8' });
const tests = (report.match(/=== RUN/g) || []).length;
const files = await globNearest(patterns);
for (const f of files) {
core.info(`Load Go report '${f}'`);
const contents = await external_fs_.promises.readFile(f, { encoding: 'utf8' });
const tests = (contents.match(/=== RUN/g) || []).length;
if (tests === 0) {
continue; // Invalid report file, trying the next one
}
const passed = (report.match(/--- PASS/g) || []).length;
const failed = (report.match(/--- FAIL/g) || []).length;
badges.push({ type: 'tests', data: { passed, failed, tests } });
const percentages = report.match(/(?<=\s)[0-9.]+(?=%)/g);
const passed = (contents.match(/--- PASS/g) || []).length;
const failed = (contents.match(/--- FAIL/g) || []).length;
const skipped = (contents.match(/--- SKIP/g) || []).length;
reports.push({ type: 'tests', data: { tests, passed, failed, skipped } });
const percentages = contents.match(/(?<=\s)[0-9.]+(?=%)/g);
if (percentages && percentages.length >= 1) {
const coverage = parseFloat(percentages.slice(-1)[0]);
badges.push({ type: 'coverage', data: { coverage } });
reports.push({ type: 'coverage', data: { coverage } });
}
break; // Successfully loaded a report file, can return now
}
core.info(`Loaded ${badges.length} Go report(s)`);
return badges;
core.info(`Loaded ${reports.length} Go report(s)`);
return reports;
}

;// CONCATENATED MODULE: ./src/reports/junit.js
Expand All @@ -33520,38 +33522,39 @@ async function getReports(root) {


/**
* Load tests reports using JUnit format.
* Load test reports using JUnit format.
* @param {string} root Root search directory
* @returns {Promise<import('./index.js').Report[]>} JUnit tests report
* @returns {Promise<Omit<TestReport, 'format'>[]>} JUnit test report
*/
async function junit_getReports(root) {
core.info('Load JUnit tests report');
core.info('Load JUnit test report');
const patterns = [
(0,external_path_.join)(root, '**/TEST-*.xml'),
(0,external_path_.join)(root, '**/report.xml'),
(0,external_path_.join)(root, '**/*test*.xml'),
(0,external_path_.join)(root, '**/*junit*.xml')
];
const reports = await globNearest(patterns);
const data = { passed: 0, failed: 0, tests: 0 };
const files = await globNearest(patterns);
const data = { tests: 0, passed: 0, failed: 0, skipped: 0 };
let count = 0;
for (const r of reports) {
core.info(`Load JUnit report '${r}'`);
const testSuites = await getTestSuiteTags(r);
for (const f of files) {
core.info(`Load JUnit report '${f}'`);
const testSuites = await getTestSuiteTags(f);
if (testSuites.length === 0) {
core.info('Report is not a valid JUnit report');
continue; // Invalid report file, trying the next one
}
for (const ts of testSuites) {
data.tests += parseInt(ts.match(/tests="([0-9]+)"/)?.[1] ?? '0');
data.failed += parseInt(ts.match(/failures="([0-9]+)"/)?.[1] ?? '0');
data.failed += parseInt(ts.match(/errors="([0-9]+)"/)?.[1] ?? '0');
data.tests += parseInt(ts.match(/tests="([0-9]+)"/)?.[1] ?? '0');
data.skipped += parseInt(ts.match(/skipped="([0-9]+)"/)?.[1] ?? '0');
}
count++;
}
data.passed = data.tests - data.failed;
data.passed = data.tests - (data.failed + data.skipped);
core.info(`Loaded ${count} JUnit report(s)`);
return [{ type: 'tests', data }];
return count > 0 ? [{ type: 'tests', data }] : [];
}

/**
Expand All @@ -33565,8 +33568,8 @@ async function junit_getReports(root) {
async function getTestSuiteTags(path) {
const testSuites = [];
let depth = 0;
const report = await external_fs_.promises.readFile(path, { encoding: 'utf8' });
const tags = report.match(/<\/?testsuite(?:[^s>][^>]+|\s*)>/g) ?? [];
const contents = await external_fs_.promises.readFile(path, { encoding: 'utf8' });
const tags = contents.match(/<\/?testsuite(?:[^s>][^>]+|\s*)>/g) ?? [];
for (const tag of tags) {
if (tag.startsWith('</')) {
depth--;
Expand Down Expand Up @@ -33597,23 +33600,24 @@ async function cobertura_getReports(root) {
(0,external_path_.join)(root, '**/*cobertura*.xml'),
(0,external_path_.join)(root, '**/*coverage*.xml')
];
const reports = await globNearest(patterns);
const badges = [];
for (const r of reports) {
core.info(`Load Cobertura report '${r}'`);
const report = await external_fs_.promises.readFile(r, { encoding: 'utf8' });
const coverageMatches = report
const files = await globNearest(patterns);
/** @type {Omit<CoverageReport, 'format'>[]} */
const reports = [];
for (const f of files) {
core.info(`Load Cobertura report '${f}'`);
const contents = await external_fs_.promises.readFile(f, { encoding: 'utf8' });
const coverageMatches = contents
.match(/(?<=<coverage[^>]+line-rate=")[0-9.]+(?=")/);
if (coverageMatches?.length !== 1) {
core.info('Report is not a valid Cobertura report');
continue; // Invalid report file, trying the next one
}
const coverage = parseFloat(coverageMatches[0]) * 100;
badges.push({ type: 'coverage', data: { coverage } });
reports.push({ type: 'coverage', data: { coverage } });
break; // Successfully loaded a report file, can return now
}
core.info(`Loaded ${badges.length} Cobertura report(s)`);
return badges;
core.info(`Loaded ${reports.length} Cobertura report(s)`);
return reports;
}

;// CONCATENATED MODULE: ./src/reports/jacoco.js
Expand All @@ -33625,22 +33629,23 @@ async function cobertura_getReports(root) {
/**
* Load coverage reports using JaCoCo format.
* @param {string} root Root search directory
* @returns {Promise<import('./index.js').Report[]>} JaCoCo coverage report
* @returns JaCoCo coverage report
*/
async function jacoco_getReports(root) {
core.info('Load JaCoCo coverage report');
const patterns = [
(0,external_path_.join)(root, '**/*jacoco*.xml'),
(0,external_path_.join)(root, '**/*coverage*.xml')
];
const reports = await globNearest(patterns);
const badges = [];
for (const r of reports) {
core.info(`Load JaCoCo report '${r}'`);
const report = await external_fs_.promises.readFile(r, { encoding: 'utf8' });
const missedMatches = report
const files = await globNearest(patterns);
/** @type {Omit<CoverageReport, 'format'>[]} */
const reports = [];
for (const f of files) {
core.info(`Load JaCoCo report '${f}'`);
const contents = await external_fs_.promises.readFile(f, { encoding: 'utf8' });
const missedMatches = contents
.match(/(?<=<counter[^>]+type="LINE"[^>]+missed=")[0-9.]+(?=")/);
const coveredMatches = report
const coveredMatches = contents
.match(/(?<=<counter[^>]+type="LINE"[^>]+covered=")[0-9.]+(?=")/);
if (!missedMatches?.length || !coveredMatches?.length) {
core.info('Report is not a valid JaCoCo report');
Expand All @@ -33649,11 +33654,11 @@ async function jacoco_getReports(root) {
const missed = parseInt(missedMatches.slice(-1)[0]);
const covered = parseInt(coveredMatches.slice(-1)[0]);
const coverage = covered * 100 / (covered + missed);
badges.push({ type: 'coverage', data: { coverage } });
reports.push({ type: 'coverage', data: { coverage } });
break; // Successfully loaded a report file, can return now
}
core.info(`Loaded ${badges.length} JaCoCo report(s)`);
return badges;
core.info(`Loaded ${reports.length} JaCoCo report(s)`);
return reports;
}

;// CONCATENATED MODULE: ./src/reports/index.js
Expand All @@ -33663,28 +33668,23 @@ async function jacoco_getReports(root) {



/**
* @typedef {{ type: string, format?: string, data: any }} Report A loaded report
* @typedef {(root: string) => Promise<Report[]>} ReportsLoader A reports loader
*/

/**
* Available report loaders
* @type {{[key: string]: {getReports: ReportsLoader}}}
* @type {{ [key: string]: { getReports: ReportsLoader } }}
*/
const loaders = { go: go_namespaceObject, junit: junit_namespaceObject, cobertura: cobertura_namespaceObject, jacoco: jacoco_namespaceObject };

/**
* Load all available reports in the current workspace.
* @returns Loaded reports
* @returns {Promise<Report[]>} Loaded reports
*/
async function reports_getReports() {
core.info('Load reports');
const all = [];
for (const id of Object.keys(loaders)) {
try {
const reports = await loaders[id].getReports(process.cwd());
all.push(...reports.map(r => ({ format: id, ...r })));
all.push(...reports.map(r => ({ ...r, format: id })));
} catch (error) {
core.warning(`Skipping ${id} report format: ${error}`);
}
Expand All @@ -33696,24 +33696,28 @@ async function reports_getReports() {
;// CONCATENATED MODULE: ./src/badges/tests.js
/**
* Build a tests badge.
* @param {*} data Badge data
* @returns {import('./index.js').Badge} Badge content
* @param {TestReportData} data Test report data
* @returns {BadgeContent} Badge content
*/
function buildBadge(data) {
const content = {};
content.message = `${data.passed} passed`;
content.color = data.passed > 0 ? 'brightgreen' : 'lightgrey';
if (data.failed > 0) {
content.message += `, ${data.failed} failed`;
content.color = 'red';
}
if (data.skipped > 0) {
content.message += `, ${data.skipped} skipped`;
}
content.color = data.failed === 0 ? 'brightgreen' : 'red';
return content;
}

;// CONCATENATED MODULE: ./src/badges/coverage.js
/**
* Build a coverage badge.
* @param {*} data Badge data
* @returns {import('./index.js').Badge} Badge content
* @param {CoverageReportData} data Coverage report data
* @returns {BadgeContent} Badge content
*/
function coverage_buildBadge(data) {
const content = {};
Expand All @@ -33739,21 +33743,16 @@ function coverage_buildBadge(data) {



/**
* @typedef {{schemaVersion?: number, label?: string, message?: string, color?: string}} Badge A generated badge
* @typedef {(data: any) => Badge} BadgeGenerator A badge generator
*/

/**
* Available badge generators
* @type {{[key: string]: {buildBadge: BadgeGenerator}}}
* @type {{ [key: string]: { buildBadge: BadgeGenerator } }}
*/
const generators = { tests: tests_namespaceObject, coverage: coverage_namespaceObject };

/**
* Build a badge file from a report.
* @param {import('../reports/index.js').Report} report Input report
* @returns {{name: string, content: Badge}} Badge file name and content
* @param {Report} report Input report
* @returns {NamedBadge} Badge name and content
*/
function badges_buildBadge(report) {
let name = `${report.format}-${report.type}.json`;
Expand All @@ -33769,12 +33768,12 @@ function badges_buildBadge(report) {
name = `${prefix}-${name}`;
}
core.info(`Build badge ${name}`);
const content = {
const badge = {
schemaVersion: 1,
label: report.type,
...generators[report.type].buildBadge(report.data)
};
return { name, content };
return { name, badge };
}

// EXTERNAL MODULE: ./node_modules/@actions/github/lib/github.js
Expand All @@ -33785,16 +33784,14 @@ var github = __nccwpck_require__(5438);

/**
* Update the Gist.
* @param {{name: string, content: import('../badges/index.js').Badge}[]} badges Badges to add to the Gist
* @param {NamedBadge[]} badges Badges to add to the Gist
*/
async function update(badges) {
core.info(`Update Gist with ${badges.length} file(s)`);
const octokit = github.getOctokit(core.getInput('token'));
const files = badges
.reduce((result, b) => {
result[b.name] = { content: JSON.stringify(b.content) };
return result;
}, {});
const files = Object.fromEntries(
badges.map((b) => [b.name, { content: JSON.stringify(b.badge) }])
);
await octokit.rest.gists.update({
gist_id: core.getInput('gist-id'),
files
Expand All @@ -33813,7 +33810,8 @@ async function main() {
.map(b => badges_buildBadge(b));
await update(badges);
} catch (error) {
core.warning(`An error occurred: ${error.message}`);
const msg = error instanceof Error ? error.message : error;
core.warning(`An error occurred: ${msg}`);
}
}

Expand Down
Loading

0 comments on commit e2b7770

Please sign in to comment.