Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check min coverage, ignore node_modules during pacakge tranversal, refactor the code, etc. #55

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ module.exports = {
"generator-star-spacing": ["error", { before: false, after: true }],
"jsx-quotes": ["error", "prefer-double"],
"max-depth": ["error", 10],
"newline-before-return": "error",
"no-alert": "error",
"no-confusing-arrow": ["error", { allowParens: false }],
"no-constant-condition": "error",
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ node_modules/
coverage/
.eslintcache
.DS_Store
.idea
badges/
29 changes: 29 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,35 @@ inputs:
app-name:
description: App name to display on comment
required: false
single-comment:
description: Add a single comment instead of multiple comments
required: false
max_lines:
description: Maximum numbers of line print
required: false
default: "15"
min_coverage:
description: Minimum coverage percentage allowed
required: false
default: "100"
exclude:
description: list of files you would like to exclude from min_coverage check
required: false
exclude_root:
description: exclude the root project coverage from min_coverage check
required: false
badge_path:
description: Output badge path.
default: build
required: false
badge_style:
description: 'Badges style: flat, classic.'
default: classic
required: false
badge_label:
description: The left label of the badge, usually static.
default: coverage
required: false
runs:
using: node16
main: dist/main.js
4 changes: 2 additions & 2 deletions dist/main.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^4.0.0",
"badgen": "^3.2.3",
"lcov-parse": "^1.0.0"
},
"devDependencies": {
Expand Down
59 changes: 59 additions & 0 deletions src/badge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { badgen } from "badgen";
import path from "path";
import { percentage } from "./lcov";

export const badge = (option, pct) => {
const { label = "coverage", style = "classic" } = option;
const colorData = {
"49c31a": [100],
"97c40f": [99.99, 90],
a0a127: [89.99, 80],
cba317: [79.99, 60],
ce0000: [59.99, 0],
};
const color = Object.keys(colorData).find(
(value) =>
(colorData[value].length === 1 && pct >= colorData[value][0]) ||
(colorData[value].length === 2 &&
pct <= colorData[value][0] &&
pct >= colorData[value][1]),
);
const badgenArgs = {
style,
label,
status: `${pct}%`,
color: color,
};
return badgen(badgenArgs);
};

export const badges = ({ rootLcov, lcovArray, options, mkDir, writeBadge }) => {
const badgeOptions = {
label: options.badgeLabel,
style: options.badgeStyle,
};
const toBadge = [];
if (lcovArray) lcovArray.forEach((x) => toBadge.push(x));
if (rootLcov) {
toBadge.push({
packageName: "root_package",
lcov: rootLcov,
});
}
if (toBadge.length === 0) {
return;
}
const dirName = path.resolve(options.badgePath);
mkDir(dirName);
for (const lcovObj of toBadge) {
const coverage = percentage(lcovObj.lcov);
const svgStr = badge(badgeOptions, coverage.toFixed(2));
const fileName = path.join(dirName, `${lcovObj.packageName}.svg`);
console.log("writing badge", fileName);
try {
writeBadge(fileName, svgStr);
} catch (err) {
console.error("Error writing badge", fileName, err.toString());
}
}
};
192 changes: 192 additions & 0 deletions src/badge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { badges } from "./badge";
import path from "path";

const someLcov = [
{
file: "/files/project/src/foo.js",
lines: {
found: 23,
hit: 21,
details: [
{
line: 20,
hit: 3,
},
{
line: 21,
hit: 3,
},
{
line: 22,
hit: 3,
},
],
},
functions: {
hit: 2,
found: 3,
details: [
{
name: "foo",
line: 19,
},
{
name: "bar",
line: 33,
},
{
name: "baz",
line: 54,
},
],
},
branches: {
hit: 3,
found: 3,
details: [
{
line: 21,
block: 0,
branch: 0,
taken: 1,
},
{
line: 21,
block: 0,
branch: 1,
taken: 2,
},
{
line: 37,
block: 1,
branch: 0,
taken: 0,
},
],
},
},
{
file: "/files/project/src/bar/baz.js",
lines: {
found: 10,
hit: 5,
details: [
{
line: 20,
hit: 0,
},
{
line: 21,
hit: 0,
},
{
line: 22,
hit: 3,
},
],
},
functions: {
hit: 2,
found: 3,
details: [
{
name: "foo",
line: 19,
},
{
name: "bar",
line: 33,
},
{
name: "baz",
line: 54,
},
],
},
},
];
const lcovArray = [
{
packageName: "SOME_PACKAGE",
lcov: someLcov,
},
{
packageName: "SOME_PACKAGE2",
lcov: someLcov,
},
{
packageName: "SOME_PACKAGE3",
lcov: someLcov,
},
];
const SOME_PATH = "SOME_PATH/";
describe("badges", function () {
test("should render badges rootLcov", () => {
const mkDir = jest.fn();
const writeBadge = jest.fn();
badges({
rootLcov: someLcov,
lcovArray: undefined,
options: { badgePath: SOME_PATH },
mkDir,
writeBadge,
});
expect(mkDir).toBeCalledWith(path.resolve(SOME_PATH));
expect(writeBadge).toBeCalledWith(
expect.stringContaining("root_package.svg"),
expect.stringContaining("<svg"),
);
});
test("should render badges lcovArray", () => {
const mkDir = jest.fn();
const writeBadge = jest.fn();
badges({
rootLcov: undefined,
lcovArray,
options: { badgePath: SOME_PATH },
mkDir,
writeBadge,
});
expect(mkDir).toBeCalledWith(path.resolve(SOME_PATH));
for (let i = 0; i < lcovArray.length; i++) {
expect(writeBadge).toHaveBeenNthCalledWith(
i + 1,
expect.stringContaining(`${lcovArray[i].packageName}.svg`),
expect.stringContaining("<svg"),
);
}
});
test("should do nothing with no data", () => {
const mkDir = jest.fn();
const writeBadge = jest.fn();
badges({
rootLcov: undefined,
lcovArray: undefined,
options: { badgePath: SOME_PATH },
mkDir,
writeBadge,
});
expect(mkDir).not.toBeCalled();
expect(writeBadge).not.toBeCalled();
});
test("should catch errors when writting", () => {
const mkDir = jest.fn();
const writeBadge = () => {
throw new Error();
};
expect(() =>
badges({
rootLcov: undefined,
lcovArray,
options: {
badgePath: SOME_PATH,
label: "other",
style: "flat",
},
mkDir,
writeBadge,
}),
).not.toThrowError();
expect(mkDir).toBeCalledWith(path.resolve(SOME_PATH));
});
});
56 changes: 56 additions & 0 deletions src/check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { percentage } from "./lcov";

const checkCoverage = (toCheck) => {
let accum = 0;
for (const lcovObj of toCheck) {
const coverage = percentage(lcovObj.lcov);
const isValidBuild = coverage >= lcovObj.minCoverage;
if (!isValidBuild) {
return { isValidBuild, coverage, name: lcovObj.packageName };
}
accum += coverage;
}
return {
isValidBuild: true,
coverage: accum / toCheck.length,
name: "average",
};
};

export const checks = ({ rootLcov, lcovArray, options, setFailed, info }) => {
const minCoverage = options.minCoverage || 100;
const excludedFiles = (options.excluded || "")
.split(" ")
.map((x) => x.trim())
.filter((x) => x.length > 0);
const toCheck = [];
if (lcovArray) {
lcovArray
.filter((x) => !excludedFiles.some((y) => x.packageName === y))
.forEach((x) => toCheck.push({ minCoverage, ...x }));
}
if (rootLcov && !options.excludeRoot) {
toCheck.unshift({
packageName: "root_package",
lcov: rootLcov,
minCoverage,
});
}
if (toCheck.length === 0) {
return;
}
const { isValidBuild, coverage, name } = checkCoverage(toCheck);
if (!isValidBuild) {
setFailed(
`${coverage.toFixed(
2,
)}% for ${name} is less than min_coverage ${minCoverage}.`,
);
} else {
info(
`Coverage: ${coverage.toFixed(
2,
)}% is greater than or equal to min_coverage ${minCoverage}.`,
);
}
};
Loading