Skip to content

Commit

Permalink
feat: switch to using Node's built in coverage (#22)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: switches to using NODE_V8_COVERAGE rather than inspector directly
  • Loading branch information
bcoe committed Sep 10, 2018
1 parent f14508e commit 3c1b92b
Show file tree
Hide file tree
Showing 15 changed files with 2,585 additions and 4,069 deletions.
24 changes: 9 additions & 15 deletions README.md
@@ -1,6 +1,6 @@
# c8 - native v8 code-coverage
# c8 - native V8 code-coverage

Code-coverage using [v8's Inspector](https://nodejs.org/dist/latest-v8.x/docs/api/inspector.html)
Code-coverage using [V8's Inspector](https://nodejs.org/dist/latest-v8.x/docs/api/inspector.html)
that's compatible with [Istanbul's reporters](https://istanbul.js.org/docs/advanced/alternative-reporters/).

Like [nyc](https://github.com/istanbuljs/nyc), c8 just magically works:
Expand All @@ -10,20 +10,14 @@ npm i c8 -g
c8 node foo.js
```

The above example will collect coverage for `foo.js` using v8's inspector.
The above example will collect coverage for `foo.js` using V8's inspector.

## Disclaimer

c8 uses bleeding edge v8 features (_it's an ongoing experiment, testing
what will eventually be possible in the realm of test coverage in Node.js_).
## c8 report

For the best experience, try running with [a canary build of Node.js](https://github.com/v8/node).
run `c8 report` to regenerate reports after `c8` has already been run.

## How it Works

Before running your application c8 creates [an inspector session](https://nodejs.org/api/inspector.html) in v8 and enables v8's
[built in coverage reporting](https://v8project.blogspot.com/2017/12/javascript-code-coverage.html).
## Disclaimer

Just before your application exits, c8 fetches the coverage information from
v8 and writes it to disk in a format compatible with
[Istanbul's reporters](https://istanbul.js.org/).
c8 uses
[bleeding edge Node.js features](https://github.com/nodejs/node/pull/22527),
make sure you're running Node.js `>= 10.10.0`.
33 changes: 19 additions & 14 deletions bin/c8.js
Expand Up @@ -4,31 +4,36 @@
const foreground = require('foreground-child')
const mkdirp = require('mkdirp')
const report = require('../lib/report')
const {resolve} = require('path')
const rimraf = require('rimraf')
const sw = require('spawn-wrap')
const {
hideInstrumenteeArgs,
hideInstrumenterArgs,
yargs
} = require('../lib/parse-args')

const instrumenterArgs = hideInstrumenteeArgs()
let argv = yargs.parse(instrumenterArgs)

const argv = yargs.parse(instrumenterArgs)
if (argv._[0] === 'report') {
argv = yargs.parse(process.argv) // support flag arguments after "report".
outputReport()
} else {
rimraf.sync(argv.tempDirectory)
mkdirp.sync(argv.tempDirectory)
process.env.NODE_V8_COVERAGE = argv.tempDirectory

const tmpDirctory = resolve(argv.coverageDirectory, './tmp')
rimraf.sync(tmpDirctory)
mkdirp.sync(tmpDirctory)

sw([require.resolve('../lib/wrap')], {
C8_ARGV: JSON.stringify(argv)
})
foreground(hideInstrumenterArgs(argv), () => {
outputReport()
})
}

foreground(hideInstrumenterArgs(argv), (out) => {
function outputReport () {
report({
include: argv.include,
exclude: argv.exclude,
reporter: Array.isArray(argv.reporter) ? argv.reporter : [argv.reporter],
coverageDirectory: argv.coverageDirectory,
watermarks: argv.watermarks
tempDirectory: argv.tempDirectory,
watermarks: argv.watermarks,
resolve: argv.resolve
})
})
}
17 changes: 13 additions & 4 deletions lib/parse-args.js
@@ -1,13 +1,13 @@
const Exclude = require('test-exclude')
const findUp = require('find-up')
const {readFileSync} = require('fs')
const { readFileSync } = require('fs')
const yargs = require('yargs')
const parser = require('yargs-parser')

const configPath = findUp.sync(['.c8rc', '.c8rc.json'])
const config = configPath ? JSON.parse(readFileSync(configPath)) : {}

yargs
yargs()
.usage('$0 [opts] [script] [opts]')
.option('reporter', {
alias: 'r',
Expand All @@ -17,17 +17,26 @@ yargs
.option('exclude', {
alias: 'x',
default: Exclude.defaultExclude,
describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported.'
describe: 'a list of specific files and directories that should be excluded from coverage (glob patterns are supported)'
})
.option('include', {
alias: 'n',
default: [],
describe: 'a list of specific files that should be covered, glob patterns are supported'
describe: 'a list of specific files that should be covered (glob patterns are supported)'
})
.option('coverage-directory', {
default: './coverage',
describe: 'directory to output coverage JSON and reports'
})
.option('temp-directory', {
default: './coverage/tmp',
describe: 'directory V8 coverage data is written to and read from'
})
.option('resolve', {
default: '',
describe: 'resolve paths to alternate base directory'
})
.command('report', 'read V8 coverage data from temp and output report')
.pkgConf('c8')
.config(config)
.demandCommand(1)
Expand Down
52 changes: 43 additions & 9 deletions lib/report.js
@@ -1,14 +1,29 @@
const Exclude = require('test-exclude')
const libCoverage = require('istanbul-lib-coverage')
const libReport = require('istanbul-lib-report')
const reports = require('istanbul-reports')
const {readdirSync, readFileSync} = require('fs')
const {resolve} = require('path')
const { readdirSync, readFileSync } = require('fs')
const { resolve, isAbsolute } = require('path')
const v8CoverageMerge = require('v8-coverage-merge')
const v8toIstanbul = require('v8-to-istanbul')

class Report {
constructor ({reporter, coverageDirectory, watermarks}) {
constructor ({
exclude,
include,
reporter,
tempDirectory,
watermarks,
resolve
}) {
this.reporter = reporter
this.coverageDirectory = coverageDirectory
this.tempDirectory = tempDirectory
this.watermarks = watermarks
this.resolve = resolve
this.exclude = Exclude({
exclude: exclude,
include: include
})
}
run () {
const map = this._getCoverageMapFromAllCoverageFiles()
Expand All @@ -25,20 +40,39 @@ class Report {
}
_getCoverageMapFromAllCoverageFiles () {
const map = libCoverage.createCoverageMap({})
const mergedResults = {}
this._loadReports().forEach((report) => {
report.result.forEach((result) => {
if (this.exclude.shouldInstrument(result.url) &&
isAbsolute(result.url)) {
if (mergedResults[result.url]) {
mergedResults[result.url] = v8CoverageMerge(
mergedResults[result.url],
result
)
} else {
mergedResults[result.url] = result
}
}
})
})

this._loadReports().forEach(function (report) {
map.merge(report)
Object.keys(mergedResults).forEach((url) => {
const result = mergedResults[url]
const path = resolve(this.resolve, result.url)
const script = v8toIstanbul(path)
script.applyCoverage(result.functions)
map.merge(script.toIstanbul())
})

return map
}
_loadReports () {
const tmpDirctory = resolve(this.coverageDirectory, './tmp')
const files = readdirSync(tmpDirctory)
const files = readdirSync(this.tempDirectory)

return files.map((f) => {
return JSON.parse(readFileSync(
resolve(tmpDirctory, f),
resolve(this.tempDirectory, f),
'utf8'
))
})
Expand Down
81 changes: 0 additions & 81 deletions lib/wrap.js

This file was deleted.

0 comments on commit 3c1b92b

Please sign in to comment.