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

Limit uncovered lines #32

Open
wants to merge 13 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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ The location of the lcov file to read the coverage report from. Defaults to
The location of the lcov file resulting from running the tests in the base
branch. When this is set a diff of the coverage percentages is shown.

##### `filter-changed-files` (**Default: false**)
If set to true, only changed files will be included in the report. Total percentage will still include all files.

##### `delete-old-comments` (**Default: false**)
If set to true, old comments will be deleted before a new comment is posted

##### `title` (**Optional**)
If included, will be added as a title for the comment produced.

##### `max-uncovered-lines` (**Optional**)
If included, will limit the number of uncovered lines displayed in the Uncovered Lines column.

## Example usage

```yml
Expand All @@ -39,3 +51,5 @@ with:
## Acknowledgements

The initial code is based on [ziishaned/jest-reporter-action](https://github.com/ziishaned/jest-reporter-action).

Changed file retrieval based on [jitterbit/get-changed-files](https://github.com/jitterbit/get-changed-files).
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ inputs:
lcov-base:
description: The location of the lcov file for the base branch
required: false
filter-changed-files:
description: Set to true to only comment with coverage on files changed in this commit
required: false
default: false
delete-old-comments:
description: Set to true to delete old Coverage Report comments
required: false
default: false
title:
description: Title to add to the comment
required: false
max-uncovered-lines:
description: Max number of uncovered lines to display in uncovered lines column (integer)
required: false
runs:
using: node12
main: dist/main.js
234 changes: 197 additions & 37 deletions dist/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -22784,11 +22784,16 @@ const b = tag("b");
const table = tag("table");
const tbody = tag("tbody");
const a = tag("a");
const h2 = tag("h2");

const fragment = function(...children) {
return children.join("")
};

function normalisePath(file) {
return file.replace(/\\/g, "/")
}

// Tabulate the lcov data in a HTML table.
function tabulate(lcov, options) {
const head = tr(
Expand All @@ -22801,7 +22806,7 @@ function tabulate(lcov, options) {
);

const folders = {};
for (const file of lcov) {
for (const file of filterAndNormaliseLcov(lcov, options)) {
const parts = file.file.replace(options.prefix, "").split("/");
const folder = parts.slice(0, -1).join("/");
folders[folder] = folders[folder] || [];
Expand All @@ -22822,6 +22827,22 @@ function tabulate(lcov, options) {
return table(tbody(head, ...rows))
}

function filterAndNormaliseLcov(lcov, options) {
return lcov
.map(file => ({
...file,
file: normalisePath(file.file),
}))
.filter(file => shouldBeIncluded(file.file, options))
}

function shouldBeIncluded(fileName, options) {
if (!options.shouldFilterChangedFiles) {
return true
}
return options.changedFiles.includes(fileName.replace(options.prefix, ""))
}

function toFolder(path) {
if (path === "") {
return ""
Expand All @@ -22833,16 +22854,19 @@ function toFolder(path) {
function getStatement(file) {
const { branches, functions, lines } = file;

return [branches, functions, lines].reduce(function(acc, curr) {
if (!curr) {
return acc
}
return [branches, functions, lines].reduce(
function(acc, curr) {
if (!curr) {
return acc
}

return {
hit: acc.hit + curr.hit,
found: acc.found + curr.found,
}
}, { hit: 0, found: 0 })
return {
hit: acc.hit + curr.hit,
found: acc.found + curr.found,
}
},
{ hit: 0, found: 0 },
)
}

function toRow(file, indent, options) {
Expand Down Expand Up @@ -22889,17 +22913,34 @@ function uncovered(file, options) {

const all = ranges([...branches, ...lines]);

var numNotIncluded = 0;
if (options.maxUncoveredLines) {
const notIncluded = all.splice(options.maxUncoveredLines);
numNotIncluded = notIncluded.length;
}

return all
const result = all
.map(function(range) {
const fragment = range.start === range.end ? `L${range.start}` : `L${range.start}-L${range.end}`;
const fragment =
range.start === range.end
? `L${range.start}`
: `L${range.start}-L${range.end}`;
const relative = file.file.replace(options.prefix, "");
const href = `https://github.com/${options.repository}/blob/${options.commit}/${relative}#${fragment}`;
const text = range.start === range.end ? range.start : `${range.start}–${range.end}`;
const text =
range.start === range.end
? range.start
: `${range.start}–${range.end}`;

return a({ href }, text)
})
.join(", ")
.join(", ");

if (numNotIncluded > 0) {
return result + ` and ${numNotIncluded} more...`
} else {
return result
}
}

function ranges(linenos) {
Expand Down Expand Up @@ -22929,14 +22970,24 @@ function ranges(linenos) {
return res
}

function comment (lcov, options) {
function comment(lcov, options) {
return fragment(
options.title ? h2(options.title) : "",
options.base
? `Coverage after merging ${b(options.head)} into ${b(options.base)}`
? `Coverage after merging ${b(options.head)} into ${b(
options.base,
)} will be`
: `Coverage for this commit`,
table(tbody(tr(th(percentage(lcov).toFixed(2), "%")))),
"\n\n",
details(summary("Coverage Report"), tabulate(lcov, options)),
details(
summary(
options.shouldFilterChangedFiles
? "Coverage Report for Changed Files"
: "Coverage Report",
),
tabulate(lcov, options),
),
)
}

Expand All @@ -22949,73 +23000,182 @@ function diff(lcov, before, options) {
const pafter = percentage(lcov);
const pdiff = pafter - pbefore;
const plus = pdiff > 0 ? "+" : "";
const arrow =
pdiff === 0
? ""
: pdiff < 0
? "▾"
: "▴";
const arrow = pdiff === 0 ? "" : pdiff < 0 ? "▾" : "▴";

return fragment(
options.title ? h2(options.title) : "",
options.base
? `Coverage after merging ${b(options.head)} into ${b(options.base)}`
? `Coverage after merging ${b(options.head)} into ${b(
options.base,
)} will be`
: `Coverage for this commit`,
table(tbody(tr(
th(pafter.toFixed(2), "%"),
th(arrow, " ", plus, pdiff.toFixed(2), "%"),
))),
table(
tbody(
tr(
th(pafter.toFixed(2), "%"),
th(arrow, " ", plus, pdiff.toFixed(2), "%"),
),
),
),
"\n\n",
details(summary("Coverage Report"), tabulate(lcov, options)),
details(
summary(
options.shouldFilterChangedFiles
? "Coverage Report for Changed Files"
: "Coverage Report",
),
tabulate(lcov, options),
),
)
}

// Get list of changed files
async function getChangedFiles(githubClient, options, context) {
if (!options.commit || !options.baseCommit) {
core_7(
`The base and head commits are missing from the payload for this ${context.eventName} event.`,
);
}

// Use GitHub's compare two commits API.
// https://developer.github.com/v3/repos/commits/#compare-two-commits
const response = await githubClient.repos.compareCommits({
base: options.baseCommit,
head: options.commit,
owner: context.repo.owner,
repo: context.repo.repo,
});

if (response.status !== 200) {
core_7(
`The GitHub API for comparing the base and head commits for this ${context.eventName} event returned ${response.status}, expected 200.`,
);
}

return response.data.files
.filter(file => file.status == "modified" || file.status == "added")
.map(file => file.filename)
}

const REQUESTED_COMMENTS_PER_PAGE = 20;

async function deleteOldComments(github, options, context) {
const existingComments = await getExistingComments(github, options, context);
for (const comment of existingComments) {
core_8(`Deleting comment: ${comment.id}`);
try {
await github.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
} catch (error) {
console.error(error);
}
}
}

async function getExistingComments(github, options, context) {
let page = 0;
let results = [];
let response;
do {
response = await github.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
per_page: REQUESTED_COMMENTS_PER_PAGE,
page: page,
});
results = results.concat(response.data);
page++;
} while (response.data.length === REQUESTED_COMMENTS_PER_PAGE)

return results.filter(
comment =>
!!comment.user &&
(!options.title || comment.body.includes(options.title)) &&
comment.body.includes("Coverage Report"),
)
}

const MAX_COMMENT_CHARS = 65536;

async function main$1() {
const token = core$1.getInput("github-token");
const githubClient = new github_2(token);
const lcovFile = core$1.getInput("lcov-file") || "./coverage/lcov.info";
const baseFile = core$1.getInput("lcov-base");
const shouldFilterChangedFiles = core$1.getInput("filter-changed-files");
const shouldDeleteOldComments = core$1.getInput("delete-old-comments");
const title = core$1.getInput("title");
const maxUncoveredLines = core$1.getInput("max-uncovered-lines");
if (maxUncoveredLines && isNaN(parseInt(maxUncoveredLines))) {
console.log(
`Invalid parameter for max-uncovered-lines '${maxUncoveredLines}'. Must be an integer. Exiting...`,
);
return
}

const raw = await fs.promises.readFile(lcovFile, "utf-8").catch(err => null);
if (!raw) {
console.log(`No coverage report found at '${lcovFile}', exiting...`);
return
}

const baseRaw = baseFile && await fs.promises.readFile(baseFile, "utf-8").catch(err => null);
const baseRaw =
baseFile && (await fs.promises.readFile(baseFile, "utf-8").catch(err => null));
if (baseFile && !baseRaw) {
console.log(`No coverage report found at '${baseFile}', ignoring...`);
}

const options = {
repository: github_1.payload.repository.full_name,
prefix: `${process.env.GITHUB_WORKSPACE}/`,
prefix: normalisePath(`${process.env.GITHUB_WORKSPACE}/`),
};

if (github_1.eventName === "pull_request") {
options.commit = github_1.payload.pull_request.head.sha;
options.baseCommit = github_1.payload.pull_request.base.sha;
options.head = github_1.payload.pull_request.head.ref;
options.base = github_1.payload.pull_request.base.ref;
} else if (github_1.eventName === "push") {
options.commit = github_1.payload.after;
options.baseCommit = github_1.payload.before;
options.head = github_1.ref;
}

options.shouldFilterChangedFiles = shouldFilterChangedFiles;
options.title = title;
if (maxUncoveredLines) {
options.maxUncoveredLines = parseInt(maxUncoveredLines);
}

if (shouldFilterChangedFiles) {
options.changedFiles = await getChangedFiles(githubClient, options, github_1);
}

const lcov = await parse$2(raw);
const baselcov = baseRaw && await parse$2(baseRaw);
const body = diff(lcov, baselcov, options);
const baselcov = baseRaw && (await parse$2(baseRaw));
const body = diff(lcov, baselcov, options).substring(0, MAX_COMMENT_CHARS);

if (shouldDeleteOldComments) {
await deleteOldComments(githubClient, options, github_1);
}

if (github_1.eventName === "pull_request") {
await new github_2(token).issues.createComment({
await githubClient.issues.createComment({
repo: github_1.repo.repo,
owner: github_1.repo.owner,
issue_number: github_1.payload.pull_request.number,
body: diff(lcov, baselcov, options),
body: body,
});
} else if (github_1.eventName === "push") {
await new github_2(token).repos.createCommitComment({
await githubClient.repos.createCommitComment({
repo: github_1.repo.repo,
owner: github_1.repo.owner,
commit_sha: options.commit,
body: diff(lcov, baselcov, options),
body: body,
});
}
}
Expand Down
Loading