UNPKG

@code-pushup/coverage-plugin

Version:
94 lines 3.95 kB
import path from 'node:path'; import { exists, getGitRoot, objectFromEntries, objectToEntries, readTextFile, toUnixNewlines, ui, } from '@code-pushup/utils'; import { mergeLcovResults } from './merge-lcov.js'; import { parseLcov } from './parse-lcov.js'; import { lcovCoverageToAuditOutput, recordToStatFunctionMapper, } from './transform.js'; // Note: condition or statement coverage is not supported in LCOV // https://stackoverflow.com/questions/48260434/is-it-possible-to-check-condition-coverage-with-gcov /** * * @param results Paths to LCOV results * @param coverageTypes types of coverage to be considered * @returns Audit outputs with complete coverage data. */ export async function lcovResultsToAuditOutputs(results, coverageTypes) { // Parse lcov files const lcovResults = await parseLcovFiles(results); // Merge multiple coverage reports for the same file const mergedResults = mergeLcovResults(lcovResults); // Calculate code coverage from all coverage results const totalCoverageStats = groupLcovRecordsByCoverageType(mergedResults, coverageTypes); const gitRoot = await getGitRoot(); return coverageTypes .map(coverageType => { const stats = totalCoverageStats[coverageType]; if (!stats) { return null; } return lcovCoverageToAuditOutput(stats, coverageType, gitRoot); }) .filter(exists); } /** * * @param results Paths to LCOV results * @returns Array of parsed LCOVRecords. */ export async function parseLcovFiles(results) { const parsedResults = (await Promise.all(results.map(async (result) => { const resultsPath = typeof result === 'string' ? result : result.resultsPath; const lcovFileContent = await readTextFile(resultsPath); if (lcovFileContent.trim() === '') { ui().logger.warning(`Coverage plugin: Empty lcov report file detected at ${resultsPath}.`); } const parsedRecords = parseLcov(toUnixNewlines(lcovFileContent)); return parsedRecords.map((record) => ({ title: record.title, file: typeof result === 'string' || result.pathToProject == null ? record.file : path.join(result.pathToProject, record.file), functions: patchInvalidStats(record, 'functions'), branches: patchInvalidStats(record, 'branches'), lines: patchInvalidStats(record, 'lines'), })); }))).flat(); if (parsedResults.length === 0) { throw new Error('All provided coverage results are empty.'); } return parsedResults; } /** * Filters out invalid `line` numbers, and ensures `hit <= found`. * @param record LCOV record * @param type Coverage type * @returns Patched stats for type in record */ function patchInvalidStats(record, type) { const stats = record[type]; return { ...stats, hit: Math.min(stats.hit, stats.found), details: stats.details.filter(detail => detail.line > 0), }; } /** * This function aggregates coverage stats from all coverage files * @param records LCOV record for each file * @param coverageTypes Types of coverage to be gathered * @returns Complete coverage stats for all defined types of coverage. */ function groupLcovRecordsByCoverageType(records, coverageTypes) { return records.reduce((acc, record) => objectFromEntries(objectToEntries(getCoverageStatsFromLcovRecord(record, coverageTypes)).map(([type, file]) => [type, [...(acc[type] ?? []), file]])), {}); } /** * @param record record file data * @param coverageTypes types of coverage to be gathered * @returns Relevant coverage data from one lcov record file. */ function getCoverageStatsFromLcovRecord(record, coverageTypes) { return objectFromEntries(coverageTypes.map(coverageType => [ coverageType, recordToStatFunctionMapper[coverageType](record), ])); } //# sourceMappingURL=lcov-runner.js.map