This commit is contained in:
Julien Catania 2024-01-28 18:00:26 +01:00 committed by GitHub
commit 60a394a30d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 593 additions and 0 deletions

View file

@ -0,0 +1,21 @@
![Tests failed](https://img.shields.io/badge/tests-4%20passed%2C%202%20failed-critical)
## ❌ <a id="user-content-r0" href="#r0">fixtures/lcov.info</a>
**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|
### ❌ <a id="user-content-r0s0" href="#r0s0">src/services/notifier/NotifierService.js</a>
```
src/services/notifier/NotifierService.js
✅ lines 100% (21/21)
✅ functions 100% (10/10)
❌ branches 50% (3/6)
```
### ❌ <a id="user-content-r0s1" href="#r0s1">src/services/notifier/providers/DiscordNotifierProvider.js</a>
```
src/services/notifier/providers/DiscordNotifierProvider.js
✅ lines 100% (17/17)
✅ functions 100% (3/3)
❌ branches 75% (3/4)
```

View file

@ -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,
}
`;

View file

@ -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

23
__tests__/lcov.test.ts Normal file
View file

@ -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)
})
})

View file

@ -32,6 +32,7 @@ inputs:
- jest-junit - jest-junit
- mocha-json - mocha-json
- swift-xunit - swift-xunit
- lcov
required: true required: true
list-suites: list-suites:
description: | description: |

250
dist/index.js generated vendored
View file

@ -268,6 +268,7 @@ const mocha_json_parser_1 = __nccwpck_require__(6043);
const swift_xunit_parser_1 = __nccwpck_require__(5366); const swift_xunit_parser_1 = __nccwpck_require__(5366);
const path_utils_1 = __nccwpck_require__(4070); const path_utils_1 = __nccwpck_require__(4070);
const github_utils_1 = __nccwpck_require__(3522); const github_utils_1 = __nccwpck_require__(3522);
const lcov_parser_1 = __nccwpck_require__(5698);
function main() { function main() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
@ -436,6 +437,8 @@ class TestReporter {
return new mocha_json_parser_1.MochaJsonParser(options); return new mocha_json_parser_1.MochaJsonParser(options);
case 'swift-xunit': case 'swift-xunit':
return new swift_xunit_parser_1.SwiftXunitParser(options); return new swift_xunit_parser_1.SwiftXunitParser(options);
case 'lcov':
return new lcov_parser_1.LcovParser(options);
default: default:
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`); throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`);
} }
@ -1290,6 +1293,120 @@ class JestJunitParser {
exports.JestJunitParser = 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: /***/ 6043:
@ -23434,6 +23551,139 @@ class Keyv extends EventEmitter {
module.exports = Keyv; 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: /***/ 9662:

29
dist/licenses.txt generated vendored
View file

@ -1053,6 +1053,35 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keyv keyv
MIT 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 lowercase-keys
MIT MIT
MIT License MIT License

16
package-lock.json generated
View file

@ -15,6 +15,7 @@
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"got": "^11.8.2", "got": "^11.8.2",
"lcov-parse": "^1.0.0",
"picomatch": "^3.0.1", "picomatch": "^3.0.1",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
@ -25,6 +26,7 @@
"@types/adm-zip": "^0.5.5", "@types/adm-zip": "^0.5.5",
"@types/github-slugger": "^1.3.0", "@types/github-slugger": "^1.3.0",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"@types/lcov-parse": "^1.0.2",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/picomatch": "^2.3.3", "@types/picomatch": "^2.3.3",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
@ -1702,6 +1704,12 @@
"@types/node": "*" "@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": { "node_modules/@types/node": {
"version": "20.10.4", "version": "20.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
@ -5858,6 +5866,14 @@
"language-subtag-registry": "~0.3.2" "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": { "node_modules/leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",

View file

@ -38,6 +38,7 @@
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"got": "^11.8.2", "got": "^11.8.2",
"lcov-parse": "^1.0.0",
"picomatch": "^3.0.1", "picomatch": "^3.0.1",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
@ -48,6 +49,7 @@
"@types/adm-zip": "^0.5.5", "@types/adm-zip": "^0.5.5",
"@types/github-slugger": "^1.3.0", "@types/github-slugger": "^1.3.0",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"@types/lcov-parse": "^1.0.2",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/picomatch": "^2.3.3", "@types/picomatch": "^2.3.3",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",

View file

@ -19,6 +19,7 @@ import {SwiftXunitParser} from './parsers/swift-xunit/swift-xunit-parser'
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils' import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
import {getCheckRunContext} from './utils/github-utils' import {getCheckRunContext} from './utils/github-utils'
import {LcovParser} from './parsers/lcov/lcov-parser'
async function main(): Promise<void> { async function main(): Promise<void> {
try { try {
@ -225,6 +226,8 @@ class TestReporter {
return new MochaJsonParser(options) return new MochaJsonParser(options)
case 'swift-xunit': case 'swift-xunit':
return new SwiftXunitParser(options) return new SwiftXunitParser(options)
case 'lcov':
return new LcovParser(options)
default: default:
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`) throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
} }

View file

@ -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<TestRunResult> {
const report = await this.parseFile(path, content)
return this.getTestRunResult(path, report)
}
private async parseFile(path: string, content: string): Promise<LcovFile[]> {
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<TestRunResult> {
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<LcovLine | LcovFunc | LcovBranch>): number {
return stat ? (stat.hit / stat.found) * 100 : 100
}
private getPartInfo(stat: LcovPart<LcovLine | LcovFunc | LcovBranch>): string {
return `${this.getPercentage(stat)}% (${stat.hit}/${stat.found})`
}
}

View file

@ -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
}

View file

@ -0,0 +1,14 @@
import parse, {LcovFile} from 'lcov-parse'
const parseProm = async (pathOrStr: string): Promise<LcovFile[]> => {
return new Promise((resolve, reject) => {
parse(pathOrStr, (err, data) => {
if (err) {
reject(err)
}
resolve(data ?? [])
})
})
}
export {parseProm}