From b8cdc6592a47bb0c785855ad4e6871dcb631682f Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 11:36:36 +1300 Subject: [PATCH 01/12] * Add filter-changed-files option which will generate the report for only changed files when set * Normalise windows paths by reversing backslashes * Add delete-old-comments option which will delete old comments * Add title option which will set an optional title * Limit comment size to github comment limit --- README.md | 11 ++ action.yml | 11 ++ dist/main.js | 209 ++++++++++++++++++----- src/comment.js | 51 ++++-- src/delete_old_comments.js | 43 +++++ src/get_changes.js | 29 ++++ src/html.js | 1 + src/index.js | 35 +++- src/tabulate.js | 48 ++++-- src/tabulate_test.js | 328 ++++++++++++++++++++++++++++++++++++- 10 files changed, 688 insertions(+), 78 deletions(-) create mode 100644 src/delete_old_comments.js create mode 100644 src/get_changes.js diff --git a/README.md b/README.md index 95a3761f..1895b4ed 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,15 @@ 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. + ## Example usage ```yml @@ -39,3 +48,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). diff --git a/action.yml b/action.yml index 159975ff..7f66e8cf 100644 --- a/action.yml +++ b/action.yml @@ -15,6 +15,17 @@ 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 runs: using: node12 main: dist/main.js diff --git a/dist/main.js b/dist/main.js index ba98502a..e1657a5a 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22523,7 +22523,7 @@ const CORE_PLUGINS = [ var rest = core$3.plugin(CORE_PLUGINS); -var context = createCommonjsModule(function (module, exports) { +var context$1 = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -22570,8 +22570,8 @@ exports.Context = Context; }); -unwrapExports(context); -var context_1 = context.Context; +unwrapExports(context$1); +var context_1 = context$1.Context; var github = createCommonjsModule(function (module, exports) { var __importDefault = (commonjsGlobal && commonjsGlobal.__importDefault) || function (mod) { @@ -22588,7 +22588,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); // Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/master/src/github.ts const rest_1 = __importDefault(rest); -const Context = __importStar(context); +const Context = __importStar(context$1); // We need this in order to extend Octokit rest_1.default.prototype = new rest_1.default(); exports.context = new Context.Context(); @@ -22784,6 +22784,7 @@ 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("") @@ -22801,7 +22802,8 @@ function tabulate(lcov, options) { ); const folders = {}; - for (const file of lcov) { + for (const file of filteredLcov(lcov, options)) { + file.file = normalisePath(file.file); const parts = file.file.replace(options.prefix, "").split("/"); const folder = parts.slice(0, -1).join("/"); folders[folder] = folders[folder] || []; @@ -22822,6 +22824,17 @@ function tabulate(lcov, options) { return table(tbody(head, ...rows)) } +function normalisePath(file) { + return file.replace(/\\/g, "/") +} + +function filteredLcov(lcov, options) { + if (!options.shouldFilterChangedFiles) { + return lcov + } + return lcov.filter(file => options.changedFiles.includes(file.file)) +} + function toFolder(path) { if (path === "") { return "" @@ -22833,16 +22846,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) { @@ -22889,13 +22905,18 @@ function uncovered(file, options) { const all = ranges([...branches, ...lines]); - return 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) }) @@ -22929,14 +22950,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), + ), ) } @@ -22949,30 +22980,115 @@ 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) { + if (!options.baseCommit || !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, context) { + const existingComments = await getExistingComments(github, 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, 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 && + comment.user.login === "github-actions[bot]" && + 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 raw = await fs.promises.readFile(lcovFile, "utf-8").catch(err => null); if (!raw) { @@ -22980,7 +23096,8 @@ async function main$1() { 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...`); } @@ -22992,30 +23109,44 @@ async function main$1() { 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 (shouldFilterChangedFiles) { + options.changedFiles = getChangedFiles(githubClient, options); + } + 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, 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, }); } } diff --git a/src/comment.js b/src/comment.js index 2dd01571..73cf3dd5 100644 --- a/src/comment.js +++ b/src/comment.js @@ -1,16 +1,26 @@ -import { details, summary, b, fragment, table, tbody, tr, th } from "./html" +import { details, summary, b, fragment, table, tbody, tr, th, h2 } from "./html" import { percentage } from "./lcov" import { tabulate } from "./tabulate" -export function comment (lcov, options) { +export 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), + ), ) } @@ -23,22 +33,31 @@ export 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), + ), ) } diff --git a/src/delete_old_comments.js b/src/delete_old_comments.js new file mode 100644 index 00000000..47d7734d --- /dev/null +++ b/src/delete_old_comments.js @@ -0,0 +1,43 @@ +import * as core from "@actions/core" + +const REQUESTED_COMMENTS_PER_PAGE = 20; + +export async function deleteOldComments(github, context) { + const existingComments = await getExistingComments(github, context) + for (const comment of existingComments) { + core.debug(`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, 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 && + comment.user.login === "github-actions[bot]" && + comment.body.includes("Coverage Report"), + ) +} diff --git a/src/get_changes.js b/src/get_changes.js new file mode 100644 index 00000000..ad94b458 --- /dev/null +++ b/src/get_changes.js @@ -0,0 +1,29 @@ +import * as core from "@actions/core" + +// Get list of changed files +export async function getChangedFiles(githubClient, options) { + if (!options.baseCommit || !options.baseCommit) { + core.setFailed( + `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.setFailed( + `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) +} diff --git a/src/html.js b/src/html.js index 1c9a95c9..4f88e8cc 100644 --- a/src/html.js +++ b/src/html.js @@ -23,6 +23,7 @@ export const table = tag("table") export const tbody = tag("tbody") export const a = tag("a") export const span = tag("span") +export const h2 = tag("h2") export const fragment = function(...children) { return children.join("") diff --git a/src/index.js b/src/index.js index 18f17a2f..faf63388 100644 --- a/src/index.js +++ b/src/index.js @@ -4,11 +4,19 @@ import { GitHub, context } from "@actions/github" import { parse } from "./lcov" import { diff } from "./comment" +import { getChangedFiles } from "./get_changes" +import { deleteOldComments } from "./delete_old_comments" + +const MAX_COMMENT_CHARS = 65536; async function main() { const token = core.getInput("github-token") + const githubClient = new GitHub(token) const lcovFile = core.getInput("lcov-file") || "./coverage/lcov.info" const baseFile = core.getInput("lcov-base") + const shouldFilterChangedFiles = core.getInput("filter-changed-files") + const shouldDeleteOldComments = core.getInput("delete-old-comments") + const title = core.getInput("title") const raw = await fs.readFile(lcovFile, "utf-8").catch(err => null) if (!raw) { @@ -16,7 +24,8 @@ async function main() { return } - const baseRaw = baseFile && await fs.readFile(baseFile, "utf-8").catch(err => null) + const baseRaw = + baseFile && (await fs.readFile(baseFile, "utf-8").catch(err => null)) if (baseFile && !baseRaw) { console.log(`No coverage report found at '${baseFile}', ignoring...`) } @@ -28,30 +37,44 @@ async function main() { if (context.eventName === "pull_request") { options.commit = context.payload.pull_request.head.sha + options.baseCommit = context.payload.pull_request.base.sha options.head = context.payload.pull_request.head.ref options.base = context.payload.pull_request.base.ref } else if (context.eventName === "push") { options.commit = context.payload.after + options.baseCommit = context.payload.before options.head = context.ref } + options.shouldFilterChangedFiles = shouldFilterChangedFiles + options.title = title + + if (shouldFilterChangedFiles) { + options.changedFiles = getChangedFiles(githubClient, options) + } + const lcov = await parse(raw) - const baselcov = baseRaw && await parse(baseRaw) + const baselcov = baseRaw && (await parse(baseRaw)) const body = diff(lcov, baselcov, options) + .substring(0, MAX_COMMENT_CHARS); + + if (shouldDeleteOldComments) { + await deleteOldComments(githubClient, context) + } if (context.eventName === "pull_request") { - await new GitHub(token).issues.createComment({ + await githubClient.issues.createComment({ repo: context.repo.repo, owner: context.repo.owner, issue_number: context.payload.pull_request.number, - body: diff(lcov, baselcov, options), + body: body, }) } else if (context.eventName === "push") { - await new GitHub(token).repos.createCommitComment({ + await githubClient.repos.createCommitComment({ repo: context.repo.repo, owner: context.repo.owner, commit_sha: options.commit, - body: diff(lcov, baselcov, options), + body: body, }) } } diff --git a/src/tabulate.js b/src/tabulate.js index adacf74f..ec52be07 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -12,7 +12,8 @@ export function tabulate(lcov, options) { ) const folders = {} - for (const file of lcov) { + for (const file of filteredLcov(lcov, options)) { + file.file = normalisePath(file.file) const parts = file.file.replace(options.prefix, "").split("/") const folder = parts.slice(0, -1).join("/") folders[folder] = folders[folder] || [] @@ -33,6 +34,17 @@ export function tabulate(lcov, options) { return table(tbody(head, ...rows)) } +function normalisePath(file) { + return file.replace(/\\/g, "/") +} + +function filteredLcov(lcov, options) { + if (!options.shouldFilterChangedFiles) { + return lcov + } + return lcov.filter(file => options.changedFiles.includes(file.file)) +} + function toFolder(path) { if (path === "") { return "" @@ -44,16 +56,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 { - hit: acc.hit + curr.hit, - found: acc.found + curr.found, - } - }, { hit: 0, found: 0 }) + 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 }, + ) } function toRow(file, indent, options) { @@ -100,13 +115,18 @@ function uncovered(file, options) { const all = ranges([...branches, ...lines]) - return 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) }) diff --git a/src/tabulate_test.js b/src/tabulate_test.js index eee1f1b5..a9fa6e0c 100644 --- a/src/tabulate_test.js +++ b/src/tabulate_test.js @@ -190,14 +190,336 @@ test("tabulate should generate a correct table", function() { { href: `https://github.com/${options.repository}/blob/${options.commit}/src/bar/baz.js#L20-L21`, }, - '20–21', + "20–21", ), - ', ', + ", ", a( { href: `https://github.com/${options.repository}/blob/${options.commit}/src/bar/baz.js#L27`, }, - '27', + "27", + ), + ), + ), + ), + ) + expect(tabulate(data, options)).toBe(html) +}) + +test("filtered tabulate should generate a correct table with only changed files", function() { + const data = [ + { + file: "/files/project/index.js", + functions: { + found: 0, + hit: 0, + details: [], + }, + }, + { + 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: 27, + hit: 0, + }, + ], + }, + functions: { + hit: 2, + found: 3, + details: [ + { + name: "foo", + line: 19, + }, + { + name: "bar", + line: 33, + }, + { + name: "baz", + line: 54, + }, + ], + }, + }, + ] + + const options = { + repository: "example/foo", + commit: "2e15bee6fe0df5003389aa5ec894ec0fea2d874a", + prefix: "/files/project/", + shouldFilterChangedFiles: true, + changedFiles: ["/files/project/src/foo.js"], + } + + const html = table( + tbody( + tr( + th("File"), + th("Stmts"), + th("Branches"), + th("Funcs"), + th("Lines"), + th("Uncovered Lines"), + ), + tr(td({ colspan: 6 }, b("src"))), + tr( + td( + "   ", + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/foo.js`, + }, + "foo.js", + ), + ), + td(b("89.66%")), + td("100%"), + td(b("66.67%")), + td(b("91.30%")), + td( + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/foo.js#L37`, + }, + 37, + ), + ), + ), + ), + ) + expect(tabulate(data, options)).toBe(html) +}) + +test("filtered tabulate should fix backwards slashes in filenames", function() { + const data = [ + { + file: "\\files\\project\\index.js", + functions: { + found: 0, + hit: 0, + details: [], + }, + }, + { + 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: 27, + hit: 0, + }, + ], + }, + functions: { + hit: 2, + found: 3, + details: [ + { + name: "foo", + line: 19, + }, + { + name: "bar", + line: 33, + }, + { + name: "baz", + line: 54, + }, + ], + }, + }, + ] + + const options = { + repository: "example/foo", + commit: "2e15bee6fe0df5003389aa5ec894ec0fea2d874a", + prefix: "/files/project/", + shouldFilterChangedFiles: true, + changedFiles: ["\\files\\project\\src\\foo.js"], + } + + const html = table( + tbody( + tr( + th("File"), + th("Stmts"), + th("Branches"), + th("Funcs"), + th("Lines"), + th("Uncovered Lines"), + ), + tr(td({ colspan: 6 }, b("src"))), + tr( + td( + "   ", + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/foo.js`, + }, + "foo.js", + ), + ), + td(b("89.66%")), + td("100%"), + td(b("66.67%")), + td(b("91.30%")), + td( + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/foo.js#L37`, + }, + 37, ), ), ), From cf7c9514375a9dab7941fae9c76af481d9930c0d Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 12:01:48 +1300 Subject: [PATCH 02/12] Pass in missing context to getChangedFIles function --- dist/main.js | 14 +++++++------- src/get_changes.js | 4 ++-- src/index.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/main.js b/dist/main.js index e1657a5a..b51ec9be 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22523,7 +22523,7 @@ const CORE_PLUGINS = [ var rest = core$3.plugin(CORE_PLUGINS); -var context$1 = createCommonjsModule(function (module, exports) { +var context = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); @@ -22570,8 +22570,8 @@ exports.Context = Context; }); -unwrapExports(context$1); -var context_1 = context$1.Context; +unwrapExports(context); +var context_1 = context.Context; var github = createCommonjsModule(function (module, exports) { var __importDefault = (commonjsGlobal && commonjsGlobal.__importDefault) || function (mod) { @@ -22588,7 +22588,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); // Originally pulled from https://github.com/JasonEtco/actions-toolkit/blob/master/src/github.ts const rest_1 = __importDefault(rest); -const Context = __importStar(context$1); +const Context = __importStar(context); // We need this in order to extend Octokit rest_1.default.prototype = new rest_1.default(); exports.context = new Context.Context(); @@ -23010,8 +23010,8 @@ function diff(lcov, before, options) { } // Get list of changed files -async function getChangedFiles(githubClient, options) { - if (!options.baseCommit || !options.baseCommit) { +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.`, ); @@ -23122,7 +23122,7 @@ async function main$1() { options.title = title; if (shouldFilterChangedFiles) { - options.changedFiles = getChangedFiles(githubClient, options); + options.changedFiles = getChangedFiles(githubClient, options, github_1); } const lcov = await parse$2(raw); diff --git a/src/get_changes.js b/src/get_changes.js index ad94b458..d8f322cb 100644 --- a/src/get_changes.js +++ b/src/get_changes.js @@ -1,8 +1,8 @@ import * as core from "@actions/core" // Get list of changed files -export async function getChangedFiles(githubClient, options) { - if (!options.baseCommit || !options.baseCommit) { +export async function getChangedFiles(githubClient, options, context) { + if (!options.commit || !options.baseCommit) { core.setFailed( `The base and head commits are missing from the payload for this ${context.eventName} event.`, ) diff --git a/src/index.js b/src/index.js index faf63388..633af73e 100644 --- a/src/index.js +++ b/src/index.js @@ -50,7 +50,7 @@ async function main() { options.title = title if (shouldFilterChangedFiles) { - options.changedFiles = getChangedFiles(githubClient, options) + options.changedFiles = getChangedFiles(githubClient, options, context) } const lcov = await parse(raw) From 348eb2efdff270b5ef89248941d04bcc87d86521 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 12:28:30 +1300 Subject: [PATCH 03/12] Add missing await --- dist/main.js | 2 +- src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/main.js b/dist/main.js index b51ec9be..bb7cd540 100644 --- a/dist/main.js +++ b/dist/main.js @@ -23122,7 +23122,7 @@ async function main$1() { options.title = title; if (shouldFilterChangedFiles) { - options.changedFiles = getChangedFiles(githubClient, options, github_1); + options.changedFiles = await getChangedFiles(githubClient, options, github_1); } const lcov = await parse$2(raw); diff --git a/src/index.js b/src/index.js index 633af73e..788cdef3 100644 --- a/src/index.js +++ b/src/index.js @@ -50,7 +50,7 @@ async function main() { options.title = title if (shouldFilterChangedFiles) { - options.changedFiles = getChangedFiles(githubClient, options, context) + options.changedFiles = await getChangedFiles(githubClient, options, context) } const lcov = await parse(raw) From 484d799f60c7f45038ebfaafa5360759c875b156 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 12:41:10 +1300 Subject: [PATCH 04/12] Log to examine why changed file matching not working --- dist/main.js | 2 ++ src/tabulate.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dist/main.js b/dist/main.js index bb7cd540..2151448c 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22832,6 +22832,8 @@ function filteredLcov(lcov, options) { if (!options.shouldFilterChangedFiles) { return lcov } + console.log(`Changed files ${options.changedFiles}`); + console.log(`Report files ${lcov.map(file => file.file)}`); return lcov.filter(file => options.changedFiles.includes(file.file)) } diff --git a/src/tabulate.js b/src/tabulate.js index ec52be07..f6175009 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -42,6 +42,8 @@ function filteredLcov(lcov, options) { if (!options.shouldFilterChangedFiles) { return lcov } + console.log(`Changed files ${options.changedFiles}`) + console.log(`Report files ${lcov.map(file => file.file)}`) return lcov.filter(file => options.changedFiles.includes(file.file)) } From 8190b5f203ba9fd1be4cdb5f36beac7968ccb05f Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 13:22:52 +1300 Subject: [PATCH 05/12] Reorder normalisation and filtering logic --- dist/main.js | 24 +++++++++++++++++------- src/tabulate.js | 24 +++++++++++++++++------- src/tabulate_test.js | 4 ++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/dist/main.js b/dist/main.js index 2151448c..7e81a9ef 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22802,8 +22802,8 @@ function tabulate(lcov, options) { ); const folders = {}; - for (const file of filteredLcov(lcov, options)) { - file.file = normalisePath(file.file); + console.log(`Changed files ${options.changedFiles}`); + 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] || []; @@ -22824,17 +22824,27 @@ 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 normalisePath(file) { return file.replace(/\\/g, "/") } -function filteredLcov(lcov, options) { +function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { - return lcov + return true } - console.log(`Changed files ${options.changedFiles}`); - console.log(`Report files ${lcov.map(file => file.file)}`); - return lcov.filter(file => options.changedFiles.includes(file.file)) + const fileNameWithoutPrefix = fileName.replace(options.prefix, ""); + console.log(`File in report: ${fileNameWithoutPrefix}`); + return options.changedFiles.includes(fileNameWithoutPrefix); } function toFolder(path) { diff --git a/src/tabulate.js b/src/tabulate.js index f6175009..8ed44885 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -12,8 +12,8 @@ export function tabulate(lcov, options) { ) const folders = {} - for (const file of filteredLcov(lcov, options)) { - file.file = normalisePath(file.file) + console.log(`Changed files ${options.changedFiles}`) + 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] || [] @@ -34,17 +34,27 @@ export 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 normalisePath(file) { return file.replace(/\\/g, "/") } -function filteredLcov(lcov, options) { +function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { - return lcov + return true } - console.log(`Changed files ${options.changedFiles}`) - console.log(`Report files ${lcov.map(file => file.file)}`) - return lcov.filter(file => options.changedFiles.includes(file.file)) + const fileNameWithoutPrefix = fileName.replace(options.prefix, "") + console.log(`File in report: ${fileNameWithoutPrefix}`) + return options.changedFiles.includes(fileNameWithoutPrefix); } function toFolder(path) { diff --git a/src/tabulate_test.js b/src/tabulate_test.js index a9fa6e0c..78038182 100644 --- a/src/tabulate_test.js +++ b/src/tabulate_test.js @@ -325,7 +325,7 @@ test("filtered tabulate should generate a correct table with only changed files" commit: "2e15bee6fe0df5003389aa5ec894ec0fea2d874a", prefix: "/files/project/", shouldFilterChangedFiles: true, - changedFiles: ["/files/project/src/foo.js"], + changedFiles: ["src/foo.js"], } const html = table( @@ -486,7 +486,7 @@ test("filtered tabulate should fix backwards slashes in filenames", function() { commit: "2e15bee6fe0df5003389aa5ec894ec0fea2d874a", prefix: "/files/project/", shouldFilterChangedFiles: true, - changedFiles: ["\\files\\project\\src\\foo.js"], + changedFiles: ["src/foo.js"], } const html = table( From 693dd523ee1669281c1cb5a7d0e40ecb72ae50e7 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 13:41:47 +1300 Subject: [PATCH 06/12] Simplify inclusion logic to ignore prefix --- dist/main.js | 7 +++---- src/tabulate.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dist/main.js b/dist/main.js index 7e81a9ef..db64cf07 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22802,7 +22802,8 @@ function tabulate(lcov, options) { ); const folders = {}; - console.log(`Changed files ${options.changedFiles}`); + console.log(`Changed files: ${options.changedFiles}`); + console.log(`Prefix: ${options.prefix}`); for (const file of filterAndNormaliseLcov(lcov, options)) { const parts = file.file.replace(options.prefix, "").split("/"); const folder = parts.slice(0, -1).join("/"); @@ -22842,9 +22843,7 @@ function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - const fileNameWithoutPrefix = fileName.replace(options.prefix, ""); - console.log(`File in report: ${fileNameWithoutPrefix}`); - return options.changedFiles.includes(fileNameWithoutPrefix); + return options.changedFiles.some(changedFile => fileName.endsWith(changedFile)); } function toFolder(path) { diff --git a/src/tabulate.js b/src/tabulate.js index 8ed44885..d35bd8e2 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -12,7 +12,8 @@ export function tabulate(lcov, options) { ) const folders = {} - console.log(`Changed files ${options.changedFiles}`) + console.log(`Changed files: ${options.changedFiles}`) + console.log(`Prefix: ${options.prefix}`) for (const file of filterAndNormaliseLcov(lcov, options)) { const parts = file.file.replace(options.prefix, "").split("/") const folder = parts.slice(0, -1).join("/") @@ -52,9 +53,7 @@ function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - const fileNameWithoutPrefix = fileName.replace(options.prefix, "") - console.log(`File in report: ${fileNameWithoutPrefix}`) - return options.changedFiles.includes(fileNameWithoutPrefix); + return options.changedFiles.some(changedFile => fileName.endsWith(changedFile)); } function toFolder(path) { From f0447e7878e0045fffd6445406249a847886fbba Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 13:53:46 +1300 Subject: [PATCH 07/12] Normalise path for prefix too --- dist/main.js | 14 ++++++-------- src/index.js | 3 ++- src/tabulate.js | 9 ++------- src/util.js | 3 +++ 4 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 src/util.js diff --git a/dist/main.js b/dist/main.js index db64cf07..0d08820b 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22790,6 +22790,10 @@ 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( @@ -22802,8 +22806,6 @@ function tabulate(lcov, options) { ); const folders = {}; - console.log(`Changed files: ${options.changedFiles}`); - console.log(`Prefix: ${options.prefix}`); for (const file of filterAndNormaliseLcov(lcov, options)) { const parts = file.file.replace(options.prefix, "").split("/"); const folder = parts.slice(0, -1).join("/"); @@ -22835,15 +22837,11 @@ function filterAndNormaliseLcov(lcov, options) { shouldBeIncluded(file.file, options)) } -function normalisePath(file) { - return file.replace(/\\/g, "/") -} - function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - return options.changedFiles.some(changedFile => fileName.endsWith(changedFile)); + return options.changedFiles.includes(fileName.replace(options.prefix, "")); } function toFolder(path) { @@ -23115,7 +23113,7 @@ async function main$1() { 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") { diff --git a/src/index.js b/src/index.js index 788cdef3..4fff1877 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import { parse } from "./lcov" import { diff } from "./comment" import { getChangedFiles } from "./get_changes" import { deleteOldComments } from "./delete_old_comments" +import { normalisePath } from "./util"; const MAX_COMMENT_CHARS = 65536; @@ -32,7 +33,7 @@ async function main() { const options = { repository: context.payload.repository.full_name, - prefix: `${process.env.GITHUB_WORKSPACE}/`, + prefix: normalisePath(`${process.env.GITHUB_WORKSPACE}/`), } if (context.eventName === "pull_request") { diff --git a/src/tabulate.js b/src/tabulate.js index d35bd8e2..2b0dbd84 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -1,4 +1,5 @@ import { th, tr, td, table, tbody, a, b, span, fragment } from "./html" +import { normalisePath } from "./util"; // Tabulate the lcov data in a HTML table. export function tabulate(lcov, options) { @@ -12,8 +13,6 @@ export function tabulate(lcov, options) { ) const folders = {} - console.log(`Changed files: ${options.changedFiles}`) - console.log(`Prefix: ${options.prefix}`) for (const file of filterAndNormaliseLcov(lcov, options)) { const parts = file.file.replace(options.prefix, "").split("/") const folder = parts.slice(0, -1).join("/") @@ -45,15 +44,11 @@ function filterAndNormaliseLcov(lcov, options) { shouldBeIncluded(file.file, options)) } -function normalisePath(file) { - return file.replace(/\\/g, "/") -} - function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - return options.changedFiles.some(changedFile => fileName.endsWith(changedFile)); + return options.changedFiles.includes(fileName.replace(options.prefix, "")); } function toFolder(path) { diff --git a/src/util.js b/src/util.js new file mode 100644 index 00000000..b1b28d71 --- /dev/null +++ b/src/util.js @@ -0,0 +1,3 @@ +export function normalisePath(file) { + return file.replace(/\\/g, "/") +} \ No newline at end of file From adfc3cec62be1b2611244fca861dc4088a6ee987 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 14:31:21 +1300 Subject: [PATCH 08/12] Improve comment deleting to factor in supplied title if there is one --- dist/main.js | 10 +++++----- src/delete_old_comments.js | 8 ++++---- src/index.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/main.js b/dist/main.js index 0d08820b..0757deea 100644 --- a/dist/main.js +++ b/dist/main.js @@ -23048,8 +23048,8 @@ async function getChangedFiles(githubClient, options, context) { const REQUESTED_COMMENTS_PER_PAGE = 20; -async function deleteOldComments(github, context) { - const existingComments = await getExistingComments(github, context); +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 { @@ -23064,7 +23064,7 @@ async function deleteOldComments(github, context) { } } -async function getExistingComments(github, context) { +async function getExistingComments(github, options, context) { let page = 0; let results = []; let response; @@ -23083,7 +23083,7 @@ async function getExistingComments(github, context) { return results.filter( comment => !!comment.user && - comment.user.login === "github-actions[bot]" && + (!!options.title || comment.body.includes(options.title)) && comment.body.includes("Coverage Report"), ) } @@ -23140,7 +23140,7 @@ async function main$1() { .substring(0, MAX_COMMENT_CHARS); if (shouldDeleteOldComments) { - await deleteOldComments(githubClient, github_1); + await deleteOldComments(githubClient, options, github_1); } if (github_1.eventName === "pull_request") { diff --git a/src/delete_old_comments.js b/src/delete_old_comments.js index 47d7734d..8642133c 100644 --- a/src/delete_old_comments.js +++ b/src/delete_old_comments.js @@ -2,8 +2,8 @@ import * as core from "@actions/core" const REQUESTED_COMMENTS_PER_PAGE = 20; -export async function deleteOldComments(github, context) { - const existingComments = await getExistingComments(github, context) +export async function deleteOldComments(github, options, context) { + const existingComments = await getExistingComments(github, options, context) for (const comment of existingComments) { core.debug(`Deleting comment: ${comment.id}`) try { @@ -18,7 +18,7 @@ export async function deleteOldComments(github, context) { } } -async function getExistingComments(github, context) { +async function getExistingComments(github, options, context) { let page = 0 let results = [] let response @@ -37,7 +37,7 @@ async function getExistingComments(github, context) { return results.filter( comment => !!comment.user && - comment.user.login === "github-actions[bot]" && + (!!options.title || comment.body.includes(options.title)) && comment.body.includes("Coverage Report"), ) } diff --git a/src/index.js b/src/index.js index 4fff1877..895defac 100644 --- a/src/index.js +++ b/src/index.js @@ -60,7 +60,7 @@ async function main() { .substring(0, MAX_COMMENT_CHARS); if (shouldDeleteOldComments) { - await deleteOldComments(githubClient, context) + await deleteOldComments(githubClient, options, context) } if (context.eventName === "pull_request") { From f9a9d65c740f4cb99f4d137616cba8b63f7bb30e Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Mon, 15 Nov 2021 14:35:06 +1300 Subject: [PATCH 09/12] Remove extra ! and run formatter --- dist/main.js | 2 +- src/delete_old_comments.js | 4 ++-- src/index.js | 7 +++---- src/tabulate.js | 9 ++++----- src/util.js | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/dist/main.js b/dist/main.js index 0757deea..05e19694 100644 --- a/dist/main.js +++ b/dist/main.js @@ -23083,7 +23083,7 @@ async function getExistingComments(github, options, context) { return results.filter( comment => !!comment.user && - (!!options.title || comment.body.includes(options.title)) && + (!options.title || comment.body.includes(options.title)) && comment.body.includes("Coverage Report"), ) } diff --git a/src/delete_old_comments.js b/src/delete_old_comments.js index 8642133c..32272513 100644 --- a/src/delete_old_comments.js +++ b/src/delete_old_comments.js @@ -1,6 +1,6 @@ import * as core from "@actions/core" -const REQUESTED_COMMENTS_PER_PAGE = 20; +const REQUESTED_COMMENTS_PER_PAGE = 20 export async function deleteOldComments(github, options, context) { const existingComments = await getExistingComments(github, options, context) @@ -37,7 +37,7 @@ async function getExistingComments(github, options, context) { return results.filter( comment => !!comment.user && - (!!options.title || comment.body.includes(options.title)) && + (!options.title || comment.body.includes(options.title)) && comment.body.includes("Coverage Report"), ) } diff --git a/src/index.js b/src/index.js index 895defac..eacae829 100644 --- a/src/index.js +++ b/src/index.js @@ -6,9 +6,9 @@ import { parse } from "./lcov" import { diff } from "./comment" import { getChangedFiles } from "./get_changes" import { deleteOldComments } from "./delete_old_comments" -import { normalisePath } from "./util"; +import { normalisePath } from "./util" -const MAX_COMMENT_CHARS = 65536; +const MAX_COMMENT_CHARS = 65536 async function main() { const token = core.getInput("github-token") @@ -56,8 +56,7 @@ async function main() { const lcov = await parse(raw) const baselcov = baseRaw && (await parse(baseRaw)) - const body = diff(lcov, baselcov, options) - .substring(0, MAX_COMMENT_CHARS); + const body = diff(lcov, baselcov, options).substring(0, MAX_COMMENT_CHARS) if (shouldDeleteOldComments) { await deleteOldComments(githubClient, options, context) diff --git a/src/tabulate.js b/src/tabulate.js index 2b0dbd84..f382fa20 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -1,5 +1,5 @@ import { th, tr, td, table, tbody, a, b, span, fragment } from "./html" -import { normalisePath } from "./util"; +import { normalisePath } from "./util" // Tabulate the lcov data in a HTML table. export function tabulate(lcov, options) { @@ -38,17 +38,16 @@ function filterAndNormaliseLcov(lcov, options) { return lcov .map(file => ({ ...file, - file: normalisePath(file.file) + file: normalisePath(file.file), })) - .filter(file => - shouldBeIncluded(file.file, options)) + .filter(file => shouldBeIncluded(file.file, options)) } function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - return options.changedFiles.includes(fileName.replace(options.prefix, "")); + return options.changedFiles.includes(fileName.replace(options.prefix, "")) } function toFolder(path) { diff --git a/src/util.js b/src/util.js index b1b28d71..24ea31e1 100644 --- a/src/util.js +++ b/src/util.js @@ -1,3 +1,3 @@ export function normalisePath(file) { return file.replace(/\\/g, "/") -} \ No newline at end of file +} From 8676aba4ad58c5e5b6ece50f786667183c285a72 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Wed, 17 Nov 2021 10:10:25 +1300 Subject: [PATCH 10/12] Allow limiting of uncovered lines --- action.yml | 3 ++ dist/main.js | 34 ++++++++++---- src/index.js | 8 ++++ src/tabulate.js | 14 +++++- src/tabulate_test.js | 105 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 10 deletions(-) diff --git a/action.yml b/action.yml index 7f66e8cf..8630f5f7 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,9 @@ inputs: 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 diff --git a/dist/main.js b/dist/main.js index 05e19694..90bfcc7b 100644 --- a/dist/main.js +++ b/dist/main.js @@ -22831,17 +22831,16 @@ function filterAndNormaliseLcov(lcov, options) { return lcov .map(file => ({ ...file, - file: normalisePath(file.file) + file: normalisePath(file.file), })) - .filter(file => - shouldBeIncluded(file.file, options)) + .filter(file => shouldBeIncluded(file.file, options)) } function shouldBeIncluded(fileName, options) { if (!options.shouldFilterChangedFiles) { return true } - return options.changedFiles.includes(fileName.replace(options.prefix, "")); + return options.changedFiles.includes(fileName.replace(options.prefix, "")) } function toFolder(path) { @@ -22914,7 +22913,13 @@ function uncovered(file, options) { const all = ranges([...branches, ...lines]); - return all + var numNotIncluded = 0; + if (options.maxUncoveredLines) { + const notIncluded = all.splice(options.maxUncoveredLines); + numNotIncluded = notIncluded.length; + } + + const result = all .map(function(range) { const fragment = range.start === range.end @@ -22929,7 +22934,13 @@ function uncovered(file, options) { return a({ href }, text) }) - .join(", ") + .join(", "); + + if (numNotIncluded > 0) { + return result + ` and ${numNotIncluded} more...` + } else { + return result + } } function ranges(linenos) { @@ -23083,7 +23094,7 @@ async function getExistingComments(github, options, context) { return results.filter( comment => !!comment.user && - (!options.title || comment.body.includes(options.title)) && + (!options.title || comment.body.includes(options.title)) && comment.body.includes("Coverage Report"), ) } @@ -23098,6 +23109,11 @@ async function main$1() { 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 && !Number.isInteger(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) { @@ -23129,6 +23145,7 @@ async function main$1() { options.shouldFilterChangedFiles = shouldFilterChangedFiles; options.title = title; + options.maxUncoveredLines = maxUncoveredLines; if (shouldFilterChangedFiles) { options.changedFiles = await getChangedFiles(githubClient, options, github_1); @@ -23136,8 +23153,7 @@ async function main$1() { const lcov = await parse$2(raw); const baselcov = baseRaw && (await parse$2(baseRaw)); - const body = diff(lcov, baselcov, options) - .substring(0, MAX_COMMENT_CHARS); + const body = diff(lcov, baselcov, options).substring(0, MAX_COMMENT_CHARS); if (shouldDeleteOldComments) { await deleteOldComments(githubClient, options, github_1); diff --git a/src/index.js b/src/index.js index eacae829..4f773fa2 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,13 @@ async function main() { const shouldFilterChangedFiles = core.getInput("filter-changed-files") const shouldDeleteOldComments = core.getInput("delete-old-comments") const title = core.getInput("title") + const maxUncoveredLines = core.getInput("max-uncovered-lines") + if (maxUncoveredLines && !Number.isInteger(maxUncoveredLines)) { + console.log( + `Invalid parameter for max-uncovered-lines '${maxUncoveredLines}'. Must be an integer. Exiting...`, + ) + return + } const raw = await fs.readFile(lcovFile, "utf-8").catch(err => null) if (!raw) { @@ -49,6 +56,7 @@ async function main() { options.shouldFilterChangedFiles = shouldFilterChangedFiles options.title = title + options.maxUncoveredLines = maxUncoveredLines if (shouldFilterChangedFiles) { options.changedFiles = await getChangedFiles(githubClient, options, context) diff --git a/src/tabulate.js b/src/tabulate.js index f382fa20..2ebee6c8 100644 --- a/src/tabulate.js +++ b/src/tabulate.js @@ -120,7 +120,13 @@ function uncovered(file, options) { const all = ranges([...branches, ...lines]) - return all + var numNotIncluded = 0 + if (options.maxUncoveredLines) { + const notIncluded = all.splice(options.maxUncoveredLines) + numNotIncluded = notIncluded.length + } + + const result = all .map(function(range) { const fragment = range.start === range.end @@ -136,6 +142,12 @@ function uncovered(file, options) { return a({ href }, text) }) .join(", ") + + if (numNotIncluded > 0) { + return result + ` and ${numNotIncluded} more...` + } else { + return result + } } function ranges(linenos) { diff --git a/src/tabulate_test.js b/src/tabulate_test.js index 78038182..ee500967 100644 --- a/src/tabulate_test.js +++ b/src/tabulate_test.js @@ -527,3 +527,108 @@ test("filtered tabulate should fix backwards slashes in filenames", function() { ) expect(tabulate(data, options)).toBe(html) }) + +test("maxUncoveredLines should limit number of uncovered lines displayed", function() { + const data = [ + { + file: "/files/project/src/bar/baz.js", + lines: { + found: 10, + hit: 5, + details: [ + { + line: 20, + hit: 0, + }, + { + line: 21, + hit: 0, + }, + { + line: 27, + hit: 0, + }, + { + line: 29, + hit: 0, + }, + { + line: 41, + hit: 0, + }, + ], + }, + functions: { + hit: 2, + found: 3, + details: [ + { + name: "foo", + line: 19, + }, + { + name: "bar", + line: 33, + }, + { + name: "baz", + line: 54, + }, + ], + }, + }, + ] + + const options = { + repository: "example/foo", + commit: "2e15bee6fe0df5003389aa5ec894ec0fea2d874a", + prefix: "/files/project/", + maxUncoveredLines: 2, + } + + const html = table( + tbody( + tr( + th("File"), + th("Stmts"), + th("Branches"), + th("Funcs"), + th("Lines"), + th("Uncovered Lines"), + ), + tr(td({ colspan: 6 }, b("src/bar"))), + tr( + td( + "   ", + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/bar/baz.js`, + }, + "baz.js", + ), + ), + td(b("53.85%")), + td("N/A"), + td(b("66.67%")), + td(b("50%")), + td( + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/bar/baz.js#L20-L21`, + }, + "20–21", + ), + ", ", + a( + { + href: `https://github.com/${options.repository}/blob/${options.commit}/src/bar/baz.js#L27`, + }, + "27", + ), + " and 2 more...", + ), + ), + ), + ) + expect(tabulate(data, options)).toBe(html) +}) From 6fb6b71e9db96256ac634c5314158d7f47b75337 Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Wed, 17 Nov 2021 10:30:37 +1300 Subject: [PATCH 11/12] Fix parameter validation --- dist/main.js | 10 +++++++--- src/index.js | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dist/main.js b/dist/main.js index 90bfcc7b..4b3a55bd 100644 --- a/dist/main.js +++ b/dist/main.js @@ -23110,8 +23110,10 @@ async function main$1() { const shouldDeleteOldComments = core$1.getInput("delete-old-comments"); const title = core$1.getInput("title"); const maxUncoveredLines = core$1.getInput("max-uncovered-lines"); - if (maxUncoveredLines && !Number.isInteger(maxUncoveredLines)) { - console.log(`Invalid parameter for max-uncovered-lines '${maxUncoveredLines}'. Must be an integer. Exiting...`); + if (maxUncoveredLines && isNaN(parseInt(maxUncoveredLines))) { + console.log( + `Invalid parameter for max-uncovered-lines '${maxUncoveredLines}'. Must be an integer. Exiting...`, + ); return } @@ -23145,7 +23147,9 @@ async function main$1() { options.shouldFilterChangedFiles = shouldFilterChangedFiles; options.title = title; - options.maxUncoveredLines = maxUncoveredLines; + if (maxUncoveredLines) { + options.maxUncoveredLines = parseInt(maxUncoveredLines); + } if (shouldFilterChangedFiles) { options.changedFiles = await getChangedFiles(githubClient, options, github_1); diff --git a/src/index.js b/src/index.js index 4f773fa2..823d596c 100644 --- a/src/index.js +++ b/src/index.js @@ -19,7 +19,7 @@ async function main() { const shouldDeleteOldComments = core.getInput("delete-old-comments") const title = core.getInput("title") const maxUncoveredLines = core.getInput("max-uncovered-lines") - if (maxUncoveredLines && !Number.isInteger(maxUncoveredLines)) { + if (maxUncoveredLines && isNaN(parseInt(maxUncoveredLines))) { console.log( `Invalid parameter for max-uncovered-lines '${maxUncoveredLines}'. Must be an integer. Exiting...`, ) @@ -56,7 +56,9 @@ async function main() { options.shouldFilterChangedFiles = shouldFilterChangedFiles options.title = title - options.maxUncoveredLines = maxUncoveredLines + if (maxUncoveredLines) { + options.maxUncoveredLines = parseInt(maxUncoveredLines) + } if (shouldFilterChangedFiles) { options.changedFiles = await getChangedFiles(githubClient, options, context) From ef9cb2c6d03d58e7a19a9b988d90c6576db0bf3b Mon Sep 17 00:00:00 2001 From: "andrew.bell" Date: Wed, 17 Nov 2021 10:53:43 +1300 Subject: [PATCH 12/12] Update README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1895b4ed..708e7bfa 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ 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