Skip to content

feat: adding check against readme #14

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

Merged
merged 12 commits into from
May 5, 2020
Merged
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
13 changes: 9 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ jobs:
node bin/only-covered main1.js main2.js
- name: Check totals 🛡
run: node bin/check-total --min 90

- name: Update code coverage badge 🥇
run: node bin/update-badge
run: node bin/check-total --min 30

- name: Set commit status using REST
# https://developer.github.com/v3/repos/statuses/
Expand All @@ -40,6 +37,14 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check coverage change from README 📫
run: node bin/set-gh-status --check-against-readme
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Update code coverage badge 🥇
run: node bin/update-badge

- name: Semantic Release 🚀
uses: cycjimmy/semantic-release-action@v2
env:
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ Which should show a commit status message like:
This script reads the code coverage summary from `coverage/coverage-summary.json` by default (you can specific a different file name using `--from` option) and posts the commit status, always passing for now.

If there is a coverage badge in the README file, you can add 2nd status check. This check will read the code coverage from the README file (by parsing the badge text), then will set a failing status check if the coverage dropped more than 1 percent. **Tip:** use this check on pull requests to ensure tests and code are updated together before merging.

```yaml
- name: Ensure coverage has not dropped 📈
run: npx set-gh-status --check-against-readme
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

![Coverage diff](images/coverage-diff.png)

## Debug

To see verbose log messages, run with `DEBUG=check-code-coverage` environment variable
Expand Down
57 changes: 54 additions & 3 deletions bin/set-gh-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

const got = require('got')
const debug = require('debug')('check-code-coverage')
const {readCoverage, toPercent} = require('..')
const {readCoverage, toPercent, badge} = require('..')

const arg = require('arg')

const args = arg({
'--from': String // input json-summary filename, by default "coverage/coverage-summary.json"
'--from': String, // input json-summary filename, by default "coverage/coverage-summary.json"
'--check-against-readme': Boolean
})
debug('args: %o', args)

Expand Down Expand Up @@ -46,6 +47,55 @@ async function setGitHubCommitStatus(options, envOptions) {
}
})
console.log('response status: %d %s', res.statusCode, res.statusMessage)

if (options.checkAgainstReadme) {
const readmePercent = badge.getCoverageFromReadme()
if (typeof readmePercent !== 'number') {
console.error('Could not get code coverage percentage from README')
process.exit(1)
}

if (pct > readmePercent) {
console.log('coverage 📈 from %d% to %d%', readmePercent, pct)
// @ts-ignore
await got.post(url, {
headers: {
authorization: `Bearer ${envOptions.token}`
},
json: {
context: 'code-coverage Δ',
state: 'success',
description: `went up from ${readmePercent}% to ${pct}%`
}
})
} else if (Math.abs(pct - readmePercent) < 1) {
console.log('coverage stayed the same %d% ~ %d%', readmePercent, pct)
// @ts-ignore
await got.post(url, {
headers: {
authorization: `Bearer ${envOptions.token}`
},
json: {
context: 'code-coverage Δ',
state: 'success',
description: `stayed the same at ${pct}%`
}
})
} else {
console.log('coverage 📉 from %d% to %d%', readmePercent, pct)
// @ts-ignore
await got.post(url, {
headers: {
authorization: `Bearer ${envOptions.token}`
},
json: {
context: 'code-coverage Δ',
state: 'failure',
description: `decreased from ${readmePercent}% to ${pct}%`
}
})
}
}
}

function checkEnvVariables(env) {
Expand All @@ -68,7 +118,8 @@ function checkEnvVariables(env) {
checkEnvVariables(process.env)

const options = {
filename: args['--file']
filename: args['--file'],
checkAgainstReadme: args['--check-against-readme']
}
const envOptions = {
token: process.env.GITHUB_TOKEN,
Expand Down
39 changes: 8 additions & 31 deletions bin/update-badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,14 @@ const path = require('path')
const fs = require('fs')
const os = require('os')
const arg = require('arg')
const {readCoverage, toPercent} = require('..')
const {readCoverage, toPercent, badge} = require('..')

const args = arg({
'--from': String, // input json-summary filename, by default "coverage/coverage-summary.json"
'--set': String // so we can convert "78%" into numbers ourselves
})
debug('args: %o', args)

const availableColors = ['red', 'yellow', 'green', 'brightgreen']

const availableColorsReStr = '(:?' + availableColors.join('|') + ')'

function getColor(coveredPercent) {
if (coveredPercent < 60) {
return 'red'
}
if (coveredPercent < 80) {
return 'yellow'
}
if (coveredPercent < 90) {
return 'green'
}
return 'brightgreen'
}

function updateBadge(args) {
let pct = 0
if (args['--set']) {
Expand All @@ -47,21 +30,15 @@ function updateBadge(args) {
const readmeText = fs.readFileSync(readmeFilename, 'utf8')

function replaceShield() {
const color = getColor(pct)
debug('for coverage %d% badge color "%s"', pct, color)
if (!availableColors.includes(color)) {
console.error('cannot pick code coverage color for %d%', pct)
console.error('color "%s" is invalid', color)
return readmeText
}

// note, Shields.io escaped '-' with '--'
const coverageRe = new RegExp(
`https://img\\.shields\\.io/badge/code--coverage-\\d+%25-${availableColorsReStr}`,
)
const coverageBadge = `https://img.shields.io/badge/code--coverage-${pct}%25-${color}`
const coverageRe = badge.getCoverageRe()
debug('coverage regex: "%s"', coverageRe)

const coverageBadge = badge.getCoverageBadge(pct)
debug('new coverage badge: "%s"', coverageBadge)
if (!coverageBadge) {
console.error('cannot form new badge for %d%', pct)
return readmeText
}

let found
let updatedReadmeText = readmeText.replace(
Expand Down
Binary file added images/coverage-diff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 61 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check

const path = require('path')
const fs = require('fs')
const debug = require('debug')('check-code-coverage')

/**
Expand Down Expand Up @@ -32,7 +33,66 @@ function toPercent(x) {
return x
}

const availableColors = ['red', 'yellow', 'green', 'brightgreen']

const availableColorsReStr = '(:?' + availableColors.join('|') + ')'

function getCoverageRe() {
// note, Shields.io escaped '-' with '--'
const coverageRe = new RegExp(
`https://img\\.shields\\.io/badge/code--coverage-\\d+%25-${availableColorsReStr}`,
)
return coverageRe
}

function getColor(coveredPercent) {
if (coveredPercent < 60) {
return 'red'
}
if (coveredPercent < 80) {
return 'yellow'
}
if (coveredPercent < 90) {
return 'green'
}
return 'brightgreen'
}

function getCoverageBadge(pct) {
const color = getColor(pct) || 'lightgrey'
debug('for coverage %d% badge color "%s"', pct, color)

const coverageBadge = `https://img.shields.io/badge/code--coverage-${pct}%25-${color}`
return coverageBadge
}

function getCoverageFromReadme() {
const readmeFilename = path.join(process.cwd(), 'README.md')
const readmeText = fs.readFileSync(readmeFilename, 'utf8')

const coverageRe = new RegExp(
`https://img\\.shields\\.io/badge/code--coverage-(\\d+)%25-${availableColorsReStr}`,
)
const matches = coverageRe.exec(readmeText)

if (!matches) {
console.log('Could not find coverage badge in README')
return
}
debug('coverage badge "%s" percentage "%s"', matches[0], matches[1])
const pct = toPercent(parseFloat(matches[1]))
debug('parsed percentage: %d', pct)
return pct
}

module.exports = {
toPercent,
readCoverage
readCoverage,
badge: {
availableColors,
availableColorsReStr,
getCoverageFromReadme,
getCoverageRe,
getCoverageBadge
}
}