diff --git a/__tests__/__outputs__/lcov-report-results.md b/__tests__/__outputs__/lcov-report-results.md
new file mode 100644
index 00000000..d8f4bfbd
--- /dev/null
+++ b/__tests__/__outputs__/lcov-report-results.md
@@ -0,0 +1,21 @@
+![Tests failed](https://img.shields.io/badge/tests-4%20passed%2C%202%20failed-critical)
+## ❌ fixtures/lcov.info
+**6** tests were completed in **0ms** with **4** passed, **2** failed and **0** skipped.
+|Test suite|Passed|Failed|Skipped|Time|
+|:---|---:|---:|---:|---:|
+|[src/services/notifier/NotifierService.js](#r0s0)|2✅|1❌||0ms|
+|[src/services/notifier/providers/DiscordNotifierProvider.js](#r0s1)|2✅|1❌||0ms|
+### ❌ src/services/notifier/NotifierService.js
+```
+src/services/notifier/NotifierService.js
+ ✅ lines 100% (21/21)
+ ✅ functions 100% (10/10)
+ ❌ branches 50% (3/6)
+```
+### ❌ src/services/notifier/providers/DiscordNotifierProvider.js
+```
+src/services/notifier/providers/DiscordNotifierProvider.js
+ ✅ lines 100% (17/17)
+ ✅ functions 100% (3/3)
+ ❌ branches 75% (3/4)
+```
\ No newline at end of file
diff --git a/__tests__/__snapshots__/lcov.test.ts.snap b/__tests__/__snapshots__/lcov.test.ts.snap
new file mode 100644
index 00000000..76e242ad
--- /dev/null
+++ b/__tests__/__snapshots__/lcov.test.ts.snap
@@ -0,0 +1,62 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`lcov report coverage report from facebook/jest test results matches snapshot 1`] = `
+TestRunResult {
+ "path": "fixtures/lcov.info",
+ "suites": [
+ TestSuiteResult {
+ "groups": [
+ TestGroupResult {
+ "name": "src/services/notifier/NotifierService.js",
+ "tests": [
+ {
+ "name": "lines 100% (21/21)",
+ "result": "success",
+ "time": 0,
+ },
+ {
+ "name": "functions 100% (10/10)",
+ "result": "success",
+ "time": 0,
+ },
+ {
+ "name": "branches 50% (3/6)",
+ "result": "failed",
+ "time": 0,
+ },
+ ],
+ },
+ ],
+ "name": "src/services/notifier/NotifierService.js",
+ "totalTime": undefined,
+ },
+ TestSuiteResult {
+ "groups": [
+ TestGroupResult {
+ "name": "src/services/notifier/providers/DiscordNotifierProvider.js",
+ "tests": [
+ {
+ "name": "lines 100% (17/17)",
+ "result": "success",
+ "time": 0,
+ },
+ {
+ "name": "functions 100% (3/3)",
+ "result": "success",
+ "time": 0,
+ },
+ {
+ "name": "branches 75% (3/4)",
+ "result": "failed",
+ "time": 0,
+ },
+ ],
+ },
+ ],
+ "name": "src/services/notifier/providers/DiscordNotifierProvider.js",
+ "totalTime": undefined,
+ },
+ ],
+ "totalTime": undefined,
+}
+`;
diff --git a/__tests__/fixtures/lcov.info b/__tests__/fixtures/lcov.info
new file mode 100644
index 00000000..8c2bb40f
--- /dev/null
+++ b/__tests__/fixtures/lcov.info
@@ -0,0 +1,92 @@
+TN:
+SF:src/services/notifier/NotifierService.js
+FN:9,(anonymous_0)
+FN:10,(anonymous_1)
+FN:26,(anonymous_2)
+FN:27,(anonymous_3)
+FN:29,(anonymous_4)
+FN:30,(anonymous_5)
+FN:46,(anonymous_6)
+FN:47,(anonymous_7)
+FN:48,(anonymous_8)
+FN:49,(anonymous_9)
+FNF:10
+FNH:10
+FNDA:1,(anonymous_0)
+FNDA:1,(anonymous_1)
+FNDA:1,(anonymous_2)
+FNDA:3,(anonymous_3)
+FNDA:3,(anonymous_4)
+FNDA:3,(anonymous_5)
+FNDA:1,(anonymous_6)
+FNDA:3,(anonymous_7)
+FNDA:3,(anonymous_8)
+FNDA:3,(anonymous_9)
+DA:9,1
+DA:10,1
+DA:11,1
+DA:13,1
+DA:14,1
+DA:26,1
+DA:27,3
+DA:29,1
+DA:30,3
+DA:31,3
+DA:33,3
+DA:34,3
+DA:46,1
+DA:47,3
+DA:48,3
+DA:51,3
+DA:53,3
+DA:54,3
+DA:58,3
+DA:61,1
+DA:64,1
+LF:21
+LH:21
+BRDA:11,0,0,1
+BRDA:11,0,1,0
+BRDA:31,1,0,3
+BRDA:31,1,1,0
+BRDA:51,2,0,3
+BRDA:51,2,1,0
+BRF:6
+BRH:3
+end_of_record
+TN:
+SF:src/services/notifier/providers/DiscordNotifierProvider.js
+FN:12,(anonymous_0)
+FN:33,(anonymous_1)
+FN:51,(anonymous_2)
+FNF:3
+FNH:3
+FNDA:1,(anonymous_0)
+FNDA:1,(anonymous_1)
+FNDA:1,(anonymous_2)
+DA:3,1
+DA:12,1
+DA:13,1
+DA:14,1
+DA:22,1
+DA:23,1
+DA:33,1
+DA:34,1
+DA:35,1
+DA:40,1
+DA:41,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:58,1
+DA:59,1
+DA:62,1
+LF:17
+LH:17
+BRDA:18,0,0,0
+BRDA:18,0,1,1
+BRDA:20,1,0,1
+BRDA:20,1,1,1
+BRF:4
+BRH:3
+end_of_record
diff --git a/__tests__/lcov.test.ts b/__tests__/lcov.test.ts
new file mode 100644
index 00000000..4678329f
--- /dev/null
+++ b/__tests__/lcov.test.ts
@@ -0,0 +1,23 @@
+import * as fs from 'fs'
+import * as path from 'path'
+
+import {getReport} from '../src/report/get-report'
+import {normalizeFilePath} from '../src/utils/path-utils'
+import {LcovParser} from '../src/parsers/lcov/lcov-parser'
+
+describe('lcov report coverage', () => {
+ it('report from facebook/jest test results matches snapshot', async () => {
+ const fixturePath = path.join(__dirname, 'fixtures', 'lcov.info')
+ const outputPath = path.join(__dirname, '__outputs__', 'lcov-report-results.md')
+ const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
+ const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
+
+ const parser = new LcovParser({parseErrors: true, trackedFiles: []})
+ const result = await parser.parse(filePath, fileContent)
+ expect(result).toMatchSnapshot()
+
+ const report = getReport([result])
+ fs.mkdirSync(path.dirname(outputPath), {recursive: true})
+ fs.writeFileSync(outputPath, report)
+ })
+})
diff --git a/action.yml b/action.yml
index 6f35ebfe..ca438587 100644
--- a/action.yml
+++ b/action.yml
@@ -32,6 +32,7 @@ inputs:
- jest-junit
- mocha-json
- swift-xunit
+ - lcov
required: true
list-suites:
description: |
diff --git a/dist/index.js b/dist/index.js
index 47a1b8d2..33588c82 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -268,6 +268,7 @@ const mocha_json_parser_1 = __nccwpck_require__(6043);
const swift_xunit_parser_1 = __nccwpck_require__(5366);
const path_utils_1 = __nccwpck_require__(4070);
const github_utils_1 = __nccwpck_require__(3522);
+const lcov_parser_1 = __nccwpck_require__(5698);
function main() {
return __awaiter(this, void 0, void 0, function* () {
try {
@@ -436,6 +437,8 @@ class TestReporter {
return new mocha_json_parser_1.MochaJsonParser(options);
case 'swift-xunit':
return new swift_xunit_parser_1.SwiftXunitParser(options);
+ case 'lcov':
+ return new lcov_parser_1.LcovParser(options);
default:
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`);
}
@@ -1290,6 +1293,120 @@ class JestJunitParser {
exports.JestJunitParser = JestJunitParser;
+/***/ }),
+
+/***/ 5698:
+/***/ (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.LcovParser = void 0;
+const test_results_1 = __nccwpck_require__(2768);
+const lcov_utils_1 = __nccwpck_require__(4750);
+class LcovParser {
+ constructor(options) {
+ this.options = options;
+ }
+ parse(path, content) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const report = yield this.parseFile(path, content);
+ return this.getTestRunResult(path, report);
+ });
+ }
+ parseFile(path, content) {
+ return __awaiter(this, void 0, void 0, function* () {
+ try {
+ return (0, lcov_utils_1.parseProm)(content);
+ //return JSON.parse(content) as LcovReport
+ }
+ catch (e) {
+ throw new Error(`Invalid JSON at ${path}\n\n${e}`);
+ }
+ });
+ }
+ getTestRunResult(path, report) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const suites = [];
+ for (const reportElement of report) {
+ const fileName = reportElement.file;
+ const statementCaseResult = {
+ name: `lines ${this.getPartInfo(reportElement.lines)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.lines) >= 80 ? 'success' : 'failed'
+ };
+ const fonctionCaseResult = {
+ name: `functions ${this.getPartInfo(reportElement.functions)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.functions) >= 80 ? 'success' : 'failed'
+ };
+ const brancheCaseResult = {
+ name: `branches ${this.getPartInfo(reportElement.branches)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.branches) >= 80 ? 'success' : 'failed'
+ };
+ const testCases = [statementCaseResult, fonctionCaseResult, brancheCaseResult];
+ const groups = [new test_results_1.TestGroupResult(fileName, testCases)];
+ const suite = new test_results_1.TestSuiteResult(fileName, groups);
+ suites.push(suite);
+ }
+ return new test_results_1.TestRunResult(path, suites);
+ });
+ }
+ getPercentage(stat) {
+ return stat ? (stat.hit / stat.found) * 100 : 100;
+ }
+ getPartInfo(stat) {
+ return `${this.getPercentage(stat)}% (${stat.hit}/${stat.found})`;
+ }
+}
+exports.LcovParser = LcovParser;
+
+
+/***/ }),
+
+/***/ 4750:
+/***/ (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());
+ });
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.parseProm = void 0;
+const lcov_parse_1 = __importDefault(__nccwpck_require__(7454));
+const parseProm = (pathOrStr) => __awaiter(void 0, void 0, void 0, function* () {
+ return new Promise((resolve, reject) => {
+ (0, lcov_parse_1.default)(pathOrStr, (err, data) => {
+ if (err) {
+ reject(err);
+ }
+ resolve(data !== null && data !== void 0 ? data : []);
+ });
+ });
+});
+exports.parseProm = parseProm;
+
+
/***/ }),
/***/ 6043:
@@ -23434,6 +23551,139 @@ class Keyv extends EventEmitter {
module.exports = Keyv;
+/***/ }),
+
+/***/ 7454:
+/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
+
+/*
+Copyright (c) 2012, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://yuilibrary.com/license/
+*/
+
+var fs = __nccwpck_require__(7147),
+ path = __nccwpck_require__(1017);
+
+/* istanbul ignore next */
+var exists = fs.exists || path.exists;
+
+var walkFile = function(str, cb) {
+ var data = [], item;
+
+ [ 'end_of_record' ].concat(str.split('\n')).forEach(function(line) {
+ line = line.trim();
+ var allparts = line.split(':'),
+ parts = [allparts.shift(), allparts.join(':')],
+ lines, fn;
+
+ switch (parts[0].toUpperCase()) {
+ case 'TN':
+ item.title = parts[1].trim();
+ break;
+ case 'SF':
+ item.file = parts.slice(1).join(':').trim();
+ break;
+ case 'FNF':
+ item.functions.found = Number(parts[1].trim());
+ break;
+ case 'FNH':
+ item.functions.hit = Number(parts[1].trim());
+ break;
+ case 'LF':
+ item.lines.found = Number(parts[1].trim());
+ break;
+ case 'LH':
+ item.lines.hit = Number(parts[1].trim());
+ break;
+ case 'DA':
+ lines = parts[1].split(',');
+ item.lines.details.push({
+ line: Number(lines[0]),
+ hit: Number(lines[1])
+ });
+ break;
+ case 'FN':
+ fn = parts[1].split(',');
+ item.functions.details.push({
+ name: fn[1],
+ line: Number(fn[0])
+ });
+ break;
+ case 'FNDA':
+ fn = parts[1].split(',');
+ item.functions.details.some(function(i, k) {
+ if (i.name === fn[1] && i.hit === undefined) {
+ item.functions.details[k].hit = Number(fn[0]);
+ return true;
+ }
+ });
+ break;
+ case 'BRDA':
+ fn = parts[1].split(',');
+ item.branches.details.push({
+ line: Number(fn[0]),
+ block: Number(fn[1]),
+ branch: Number(fn[2]),
+ taken: ((fn[3] === '-') ? 0 : Number(fn[3]))
+ });
+ break;
+ case 'BRF':
+ item.branches.found = Number(parts[1]);
+ break;
+ case 'BRH':
+ item.branches.hit = Number(parts[1]);
+ break;
+ }
+
+ if (line.indexOf('end_of_record') > -1) {
+ data.push(item);
+ item = {
+ lines: {
+ found: 0,
+ hit: 0,
+ details: []
+ },
+ functions: {
+ hit: 0,
+ found: 0,
+ details: []
+ },
+ branches: {
+ hit: 0,
+ found: 0,
+ details: []
+ }
+ };
+ }
+ });
+
+ data.shift();
+
+ if (data.length) {
+ cb(null, data);
+ } else {
+ cb('Failed to parse string');
+ }
+};
+
+var parse = function(file, cb) {
+ exists(file, function(x) {
+ if (!x) {
+ return walkFile(file, cb);
+ }
+ fs.readFile(file, 'utf8', function(err, str) {
+ walkFile(str, cb);
+ });
+ });
+
+};
+
+
+module.exports = parse;
+module.exports.source = walkFile;
+
+
/***/ }),
/***/ 9662:
diff --git a/dist/licenses.txt b/dist/licenses.txt
index 0eeea049..27c94182 100644
--- a/dist/licenses.txt
+++ b/dist/licenses.txt
@@ -1053,6 +1053,35 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keyv
MIT
+lcov-parse
+BSD-3-Clause
+Copyright 2012 Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Yahoo! Inc. nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
lowercase-keys
MIT
MIT License
diff --git a/package-lock.json b/package-lock.json
index 3edad7e1..f2176fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"adm-zip": "^0.5.10",
"fast-glob": "^3.3.2",
"got": "^11.8.2",
+ "lcov-parse": "^1.0.0",
"picomatch": "^3.0.1",
"xml2js": "^0.6.2"
},
@@ -25,6 +26,7 @@
"@types/adm-zip": "^0.5.5",
"@types/github-slugger": "^1.3.0",
"@types/jest": "^29.5.11",
+ "@types/lcov-parse": "^1.0.2",
"@types/node": "^20.10.4",
"@types/picomatch": "^2.3.3",
"@types/xml2js": "^0.4.14",
@@ -1702,6 +1704,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/lcov-parse": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/lcov-parse/-/lcov-parse-1.0.2.tgz",
+ "integrity": "sha512-tdoxiYm04XdDEdR7UMwkWj78UAVo9U2IOcxI6tmX2/s9TK/ue/9T8gbpS/07yeWyVkVO0UumFQ5EUIBQbVejzQ==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "20.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
@@ -5858,6 +5866,14 @@
"language-subtag-registry": "~0.3.2"
}
},
+ "node_modules/lcov-parse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
+ "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==",
+ "bin": {
+ "lcov-parse": "bin/cli.js"
+ }
+ },
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
diff --git a/package.json b/package.json
index d1f86a9e..3cdf042d 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"adm-zip": "^0.5.10",
"fast-glob": "^3.3.2",
"got": "^11.8.2",
+ "lcov-parse": "^1.0.0",
"picomatch": "^3.0.1",
"xml2js": "^0.6.2"
},
@@ -48,6 +49,7 @@
"@types/adm-zip": "^0.5.5",
"@types/github-slugger": "^1.3.0",
"@types/jest": "^29.5.11",
+ "@types/lcov-parse": "^1.0.2",
"@types/node": "^20.10.4",
"@types/picomatch": "^2.3.3",
"@types/xml2js": "^0.4.14",
diff --git a/src/main.ts b/src/main.ts
index c3c89efd..9c39dc75 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -19,6 +19,7 @@ import {SwiftXunitParser} from './parsers/swift-xunit/swift-xunit-parser'
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
import {getCheckRunContext} from './utils/github-utils'
+import {LcovParser} from './parsers/lcov/lcov-parser'
async function main(): Promise {
try {
@@ -225,6 +226,8 @@ class TestReporter {
return new MochaJsonParser(options)
case 'swift-xunit':
return new SwiftXunitParser(options)
+ case 'lcov':
+ return new LcovParser(options)
default:
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
}
diff --git a/src/parsers/lcov/lcov-parser.ts b/src/parsers/lcov/lcov-parser.ts
new file mode 100644
index 00000000..bee98f84
--- /dev/null
+++ b/src/parsers/lcov/lcov-parser.ts
@@ -0,0 +1,59 @@
+import {ParseOptions, TestParser} from '../../test-parser'
+
+import {TestCaseResult, TestGroupResult, TestRunResult, TestSuiteResult} from '../../test-results'
+import {parseProm} from './lcov-utils'
+import {LcovBranch, LcovFile, LcovFunc, LcovLine, LcovPart} from 'lcov-parse'
+
+export class LcovParser implements TestParser {
+ constructor(readonly options: ParseOptions) {}
+ async parse(path: string, content: string): Promise {
+ const report = await this.parseFile(path, content)
+ return this.getTestRunResult(path, report)
+ }
+
+ private async parseFile(path: string, content: string): Promise {
+ try {
+ return parseProm(content)
+ //return JSON.parse(content) as LcovReport
+ } catch (e) {
+ throw new Error(`Invalid JSON at ${path}\n\n${e}`)
+ }
+ }
+ private async getTestRunResult(path: string, report: LcovFile[]): Promise {
+ const suites: TestSuiteResult[] = []
+
+ for (const reportElement of report) {
+ const fileName = reportElement.file
+
+ const statementCaseResult: TestCaseResult = {
+ name: `lines ${this.getPartInfo(reportElement.lines)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.lines) >= 80 ? 'success' : 'failed'
+ }
+ const fonctionCaseResult: TestCaseResult = {
+ name: `functions ${this.getPartInfo(reportElement.functions)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.functions) >= 80 ? 'success' : 'failed'
+ }
+ const brancheCaseResult: TestCaseResult = {
+ name: `branches ${this.getPartInfo(reportElement.branches)}`,
+ time: 0,
+ result: this.getPercentage(reportElement.branches) >= 80 ? 'success' : 'failed'
+ }
+
+ const testCases: TestCaseResult[] = [statementCaseResult, fonctionCaseResult, brancheCaseResult]
+ const groups: TestGroupResult[] = [new TestGroupResult(fileName, testCases)]
+ const suite: TestSuiteResult = new TestSuiteResult(fileName, groups)
+
+ suites.push(suite)
+ }
+ return new TestRunResult(path, suites)
+ }
+
+ private getPercentage(stat: LcovPart): number {
+ return stat ? (stat.hit / stat.found) * 100 : 100
+ }
+ private getPartInfo(stat: LcovPart): string {
+ return `${this.getPercentage(stat)}% (${stat.hit}/${stat.found})`
+ }
+}
diff --git a/src/parsers/lcov/lcov-types.ts b/src/parsers/lcov/lcov-types.ts
new file mode 100644
index 00000000..9fbe2ce3
--- /dev/null
+++ b/src/parsers/lcov/lcov-types.ts
@@ -0,0 +1,21 @@
+export interface LcovReport {
+ [str: string]: {
+ path: string
+ statementMap: unknown
+ fnMap: unknown
+ branchMap: unknown
+ s: CovStats
+ f: CovStats
+ b: CovStats
+ }
+}
+
+export interface CovStats {
+ [str: string]: number
+}
+
+export interface CovParsedStat {
+ max: number
+ nonCovered: number
+ percentage: number
+}
diff --git a/src/parsers/lcov/lcov-utils.ts b/src/parsers/lcov/lcov-utils.ts
new file mode 100644
index 00000000..864a5244
--- /dev/null
+++ b/src/parsers/lcov/lcov-utils.ts
@@ -0,0 +1,14 @@
+import parse, {LcovFile} from 'lcov-parse'
+
+const parseProm = async (pathOrStr: string): Promise => {
+ return new Promise((resolve, reject) => {
+ parse(pathOrStr, (err, data) => {
+ if (err) {
+ reject(err)
+ }
+ resolve(data ?? [])
+ })
+ })
+}
+
+export {parseProm}