Skip to content

Commit

Permalink
feat(junit): parse folder with multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
aGallea committed Apr 25, 2023
1 parent ac1ec4a commit 60dc9cb
Show file tree
Hide file tree
Showing 17 changed files with 1,472 additions and 70 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Following inputs can be used as `step.with` keys
| `github-token` | String | True | | GH authentication token |
| `title` | String | False | Tests Report | Title for the comment |
| `cobertura-path` | String | False | ./coverage/cobertura-coverage.xml | The location of the cobertura coverage xml file |
| `junit-path` | String | False | ./coverage/junit.xml | The location of the junit xml file |
| `junit-path` | String | False | ./coverage/junit.xml | The location of the junit xml file or folder |
| `jacoco-path` | String | False | ./target/site/jacoco/jacoco.xml | The location of the jacoco xml file |
| `clover-path` | String | False | ./coverage/clover.xml | The location of the clover coverage xml file |
| `lcov-path` | String | False | ./coverage/lcov.info | The location of the lcov info file |
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ inputs:
default: ./target/site/jacoco/jacoco.xml
required: false
junit-path:
description: The location of the junit xml file
description: The location of the junit xml file or folder
default: ./coverage/junit.xml
required: false
show-junit:
Expand Down
135 changes: 107 additions & 28 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ const main = async () => {
jacoco: eventInfo.diffcoverRef === 'jacoco'
? await (0, jacoco_1.parseFile)(eventInfo.jacocoPath)
: [],
junit: eventInfo.showJunit ? await (0, junit_1.parseFile)(eventInfo.junitPath) : undefined,
junit: eventInfo.showJunit ? await (0, junit_1.parse)(eventInfo.junitPath) : undefined,
};
const changedFile = await (0, changedFiles_1.getChangedFiles)(eventInfo);
const diffInfo = await (0, diffCover_1.diffCover)(eventInfo, changedFile, coverageInfo);
Expand Down Expand Up @@ -664,7 +664,7 @@ const parseContent = (xml) => {
const parseFile = async (file) => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no clover file specified');
resolve([]);
}
else {
Expand Down Expand Up @@ -844,7 +844,7 @@ const parseContent = (xml, pwd) => {
const parseFile = async (file, pwd) => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no cobertura file specified');
resolve([]);
}
else {
Expand Down Expand Up @@ -1010,7 +1010,7 @@ const parseContent = (xml) => {
const parseFile = async (file) => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no jacoco file specified');
resolve([]);
}
else {
Expand Down Expand Up @@ -1072,7 +1072,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.parseFile = void 0;
exports.parse = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs_1 = __importDefault(__nccwpck_require__(7147));
const xml2js_1 = __importDefault(__nccwpck_require__(6189));
Expand Down Expand Up @@ -1135,35 +1135,114 @@ const parseContent = (xml) => {
});
});
};
const parse = async (path) => {
if (!path || path === '') {
core.info('no junit file/folder specified');
return undefined;
}
if (fs_1.default.lstatSync(path).isFile()) {
return parseFile(path);
}
else if (fs_1.default.lstatSync(path).isDirectory()) {
return parseFolder(path);
}
};
exports.parse = parse;
const parseFile = async (file) => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
resolve(undefined);
}
else {
fs_1.default.readFile(file, 'utf8', async (err, data) => {
if (err) {
core.error(`failed to read file: ${file}. error: ${err.message}`);
reject(err);
fs_1.default.readFile(file, 'utf8', async (err, data) => {
if (err) {
core.error(`failed to read file: ${file}. error: ${err.message}`);
reject(err);
}
else {
try {
const info = await parseContent(data);
// console.log('====== junit ======');
// console.log(JSON.stringify(info, null, 2));
resolve(info);
}
catch (error) {
core.error(`failed to parseContent. err: ${error.message}`);
reject(error);
}
}
});
});
};
const parseFolder = async (folder) => {
const mergedTestSuites = {
$: {},
testsuite: [],
};
const files = fs_1.default.readdirSync(folder);
for (const file of files) {
try {
if (file.endsWith('.xml')) {
const filePath = `${folder}/${file}`;
const testSuiteArray = await getTestsuiteList(filePath);
if (testSuiteArray.length === 0) {
core.warning(`No tests found in file: ${filePath}`);
}
else {
try {
const info = await parseContent(data);
// console.log('====== junit ======');
// console.log(JSON.stringify(info, null, 2));
resolve(info);
}
catch (error) {
core.error(`failed to parseContent. err: ${error.message}`);
reject(error);
}
mergedTestSuites.testsuite.push(...testSuiteArray);
}
});
}
}
});
catch (error) {
core.error(`failed to parse folder file: ${folder}/${file}. error: ${error.message}`);
}
}
mergedTestSuites.$ = buildMainContent(mergedTestSuites.testsuite);
return unpackage(mergedTestSuites);
};
const getTestsuiteList = async (filename) => {
try {
const testsuiteList = [];
const xmlContent = fs_1.default.readFileSync(filename, 'utf8');
const parseResult = await xml2js_1.default.parseStringPromise(xmlContent);
if (Object.keys(parseResult)?.[0] === 'testsuite') {
testsuiteList.push(parseResult.testsuite);
}
else if (Object.keys(parseResult)?.[0] === 'testsuites') {
for (const testsuite of parseResult.testsuites.testsuite) {
testsuiteList.push(testsuite);
}
}
return testsuiteList;
}
catch (error) {
core.error(`failed to read file: ${filename}. error: ${error.message}`);
return [];
}
};
const buildMainContent = (testSuiteList) => {
const main = {
tests: 0,
failures: 0,
errors: 0,
skipped: 0,
name: '',
time: 0,
};
for (const testSuite of testSuiteList) {
main.tests += +testSuite.$.tests;
main.failures += +testSuite.$.failures;
main.errors += +testSuite.$.errors;
main.skipped += +testSuite.$.skipped;
if (main.time < +testSuite.$.time) {
main.time = +testSuite.$.time;
}
}
return {
tests: `${main.tests}`,
failures: `${main.failures}`,
errors: `${main.errors}`,
skipped: `${main.skipped}`,
name: '',
time: `${main.time}`,
};
};
exports.parseFile = parseFile;
//# sourceMappingURL=junit.js.map

/***/ }),
Expand Down Expand Up @@ -1305,7 +1384,7 @@ const parseContent = (str) => {
function parseFile(file) {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no lcov file specified');
resolve([]);
}
else {
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { parseFile as parseLcovFile } from './parsers/lcov';
import { parseFile as parseCoberturaFile } from './parsers/cobertura';
import { parseFile as parseCloverFile } from './parsers/clover';
import { parseFile as parseJacocoFile } from './parsers/jacoco';
import { parseFile as parseJunitFile } from './parsers/junit';
import { parse as parseJunit } from './parsers/junit';
import { buildBody, commentCoverage } from './commentCoverage';
import * as core from '@actions/core';

Expand All @@ -28,7 +28,7 @@ export const main = async (): Promise<void> => {
eventInfo.diffcoverRef === 'jacoco'
? await parseJacocoFile(eventInfo.jacocoPath)
: [],
junit: eventInfo.showJunit ? await parseJunitFile(eventInfo.junitPath) : undefined,
junit: eventInfo.showJunit ? await parseJunit(eventInfo.junitPath) : undefined,
};
const changedFile = await getChangedFiles(eventInfo);

Expand Down
2 changes: 1 addition & 1 deletion src/parsers/clover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const parseContent = (xml: any): Promise<CoverInfo[]> => {
export const parseFile = async (file: string): Promise<CoverInfo[]> => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no clover file specified');
resolve([]);
} else {
fs.readFile(
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/cobertura.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const parseContent = (xml: string, pwd: string): Promise<CoverInfo[]> => {
export const parseFile = async (file: string, pwd: string): Promise<CoverInfo[]> => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no cobertura file specified');
resolve([]);
} else {
fs.readFile(
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/jacoco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const parseContent = (xml: string): Promise<CoverInfo[]> => {
export const parseFile = async (file: string): Promise<CoverInfo[]> => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no jacoco file specified');
resolve([]);
} else {
fs.readFile(
Expand Down
127 changes: 102 additions & 25 deletions src/parsers/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,32 +72,109 @@ const parseContent = (xml: string): Promise<Junit> => {
});
};

export const parseFile = async (file: string): Promise<Junit | undefined> => {
export const parse = async (path: string): Promise<Junit | undefined> => {
if (!path || path === '') {
core.info('no junit file/folder specified');
return undefined;
}
if (fs.lstatSync(path).isFile()) {
return parseFile(path);
} else if (fs.lstatSync(path).isDirectory()) {
return parseFolder(path);
}
};

const parseFile = async (file: string): Promise<Junit | undefined> => {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
resolve(undefined);
} else {
fs.readFile(
file,
'utf8',
async (err: NodeJS.ErrnoException | null, data: string) => {
if (err) {
core.error(`failed to read file: ${file}. error: ${err.message}`);
reject(err);
} else {
try {
const info = await parseContent(data);
// console.log('====== junit ======');
// console.log(JSON.stringify(info, null, 2));
resolve(info);
} catch (error) {
core.error(`failed to parseContent. err: ${error.message}`);
reject(error);
}
}
},
fs.readFile(file, 'utf8', async (err: NodeJS.ErrnoException | null, data: string) => {
if (err) {
core.error(`failed to read file: ${file}. error: ${err.message}`);
reject(err);
} else {
try {
const info = await parseContent(data);
// console.log('====== junit ======');
// console.log(JSON.stringify(info, null, 2));
resolve(info);
} catch (error) {
core.error(`failed to parseContent. err: ${error.message}`);
reject(error);
}
}
});
});
};

const parseFolder = async (folder: string): Promise<Junit | undefined> => {
const mergedTestSuites: any = {
$: {},
testsuite: [],
};
const files = fs.readdirSync(folder);
for (const file of files) {
try {
if (file.endsWith('.xml')) {
const filePath = `${folder}/${file}`;
const testSuiteArray = await getTestsuiteList(filePath);
if (testSuiteArray.length === 0) {
core.warning(`No tests found in file: ${filePath}`);
} else {
mergedTestSuites.testsuite.push(...testSuiteArray);
}
}
} catch (error) {
core.error(
`failed to parse folder file: ${folder}/${file}. error: ${error.message}`,
);
}
});
}
mergedTestSuites.$ = buildMainContent(mergedTestSuites.testsuite);
return unpackage(mergedTestSuites);
};

const getTestsuiteList = async (filename: string) => {
try {
const testsuiteList: any[] = [];
const xmlContent = fs.readFileSync(filename, 'utf8');
const parseResult = await parseString.parseStringPromise(xmlContent);
if (Object.keys(parseResult)?.[0] === 'testsuite') {
testsuiteList.push(parseResult.testsuite);
} else if (Object.keys(parseResult)?.[0] === 'testsuites') {
for (const testsuite of parseResult.testsuites.testsuite) {
testsuiteList.push(testsuite);
}
}
return testsuiteList;
} catch (error) {
core.error(`failed to read file: ${filename}. error: ${error.message}`);
return [];
}
};

const buildMainContent = (testSuiteList: any[]) => {
const main = {
tests: 0,
failures: 0,
errors: 0,
skipped: 0,
name: '',
time: 0,
};
for (const testSuite of testSuiteList) {
main.tests += +testSuite.$.tests;
main.failures += +testSuite.$.failures;
main.errors += +testSuite.$.errors;
main.skipped += +testSuite.$.skipped;
if (main.time < +testSuite.$.time) {
main.time = +testSuite.$.time;
}
}
return {
tests: `${main.tests}`,
failures: `${main.failures}`,
errors: `${main.errors}`,
skipped: `${main.skipped}`,
name: '',
time: `${main.time}`,
};
};
2 changes: 1 addition & 1 deletion src/parsers/lcov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const parseContent = (str: string): CoverInfo[] => {
export function parseFile(file: string): Promise<CoverInfo[]> {
return new Promise((resolve, reject) => {
if (!file || file === '') {
core.info('no file specified');
core.info('no lcov file specified');
resolve([]);
} else {
fs.readFile(
Expand Down
1 change: 1 addition & 0 deletions test/assets/junit/invalidXmlContent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
some invalid xml content.
Loading

0 comments on commit 60dc9cb

Please sign in to comment.