From 4f7cf162a02d68e44e863eb2714baf10eb6143d4 Mon Sep 17 00:00:00 2001 From: Ade Attwood Date: Wed, 16 Aug 2023 19:39:25 +0100 Subject: [PATCH] feat: add diffing of lcov files --- README.md | 26 +++++++++++++++++++++++++- src/index.ts | 20 ++++++++++++++++++-- src/lcov-diff.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/lcov-diff.ts diff --git a/README.md b/README.md index dac4375..fdd1451 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Diff Cov -Simple CLI to print diffs highlighted with test coverage status +Simple CLI to print git and lcov diffs highlighted with test coverage status ![Example Output](assets/example-output.png) @@ -45,6 +45,8 @@ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/0.x ## Usage +### Git diff + Before you run `diff-cov` you must run your test suite with coverage and output a `lcov` coverage file. You must also have all your changes committed to ensure it's included in the output. @@ -62,3 +64,25 @@ diff-cov --coverageFile coverage/lcov.info A report is printed at the bottom and colored with a threshold of `90%` anything below this percentage coverage will be colored red. + +### LCov diff + +You can also print the coverage difference between to lcov.info files. To use +this run your tests with coverage for a first time. After its finished you can +copy your lcov.info file somewhere for later for example: + +```shell +cp ./lcov.info /tmp/lcov.info +``` + +Then you can work on your test and run the tests once more with coverage to +generate you a new `lcov.info` file. Then you can print the difference in +coverage between the two files with: + +```shell +diff-cov --compare /tmp/lcov.info +``` + +This can come in handy when you need to find out what a test is testing. You +can create your base coverage file, comment out a test, run the tests again and +diff the results. diff --git a/src/index.ts b/src/index.ts index 1f7a907..5391e8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { hideBin } from "yargs/helpers"; import exec from "./exec"; import report from "./report"; +import lcovDiff from "./lcov-diff"; // eslint-disable-next-line @typescript-eslint/no-var-requires const parseDiff = require("./diff-parser"); @@ -21,11 +22,16 @@ const options: { [key: string]: Options } = { description: "The path to the lcov report file", type: "string", }, + compare: { + description: "The path to the lcov report you wish to compare to the `coverage-file`", + type: "string", + }, }; type Argv = Arguments< Partial<{ coverageFile: string; + compare?: string; }> >; @@ -36,6 +42,10 @@ export const validate = async (argv: Argv) => { `Please ensure you have run tests with coverage enabled.` ); } + + if (argv.compare && !fs.existsSync(argv.compare)) { + return new Error(`Lcov compare file must be a valid file '${argv.compare}' provided.`); + } }; export const run = async (argv = process.argv) => { @@ -45,13 +55,19 @@ export const run = async (argv = process.argv) => { return error(validationError.message); } + const baseCoverage = await parseLcov.default(parsed.coverageFile); + + if (parsed.compare) { + const compareCoverage = await parseLcov.default(parsed.compare); + return lcovDiff(baseCoverage, compareCoverage); + } + const diffText = await exec(`git diff origin/HEAD...HEAD`); if (diffText.code > 0) { return error("Error loading the diff\n\n" + diffText.stderr); } const diff = parseDiff.default(diffText.stdout); - const coverage = await parseLcov.default(parsed.coverageFile); - report(diff, coverage); + report(diff, baseCoverage); }; diff --git a/src/lcov-diff.ts b/src/lcov-diff.ts new file mode 100644 index 0000000..9851dad --- /dev/null +++ b/src/lcov-diff.ts @@ -0,0 +1,40 @@ +const buildCoverageSet = (report: any) => { + const set = new Set(); + for (const coverage of report) { + for (const detail of coverage.lines.details) { + if (detail.hit > 0) { + set.add(`${coverage.file}:${detail.line}`); + } + } + } + + return set; +}; + +function setDiff(a: Set, b: Set) { + return new Set([...a].filter((x) => !b.has(x))); +} + +export const lcovDiff = async (baseCoverage: any, compareCoverage: any) => { + const baseSet = buildCoverageSet(baseCoverage); + const compareSet = buildCoverageSet(compareCoverage); + + const map = new Map(); + + const added = setDiff(baseSet, compareSet); + for (const key of added) { + map.set(key, 1); + } + + const removed = setDiff(compareSet, baseSet); + for (const key of removed) { + map.set(key, 0); + } + + for (const [key, value] of [...map.entries()].sort()) { + const color = value > 0 ? "\x1b[32m" : "\x1b[31m"; + console.log(color, key, "\x1b[0m"); + } +}; + +export default lcovDiff;