Skip to content
Closed
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,18 @@ Before running your application c8 creates [an inspector session](https://nodejs

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/).
[Istanbul's reporters](https://istanbul.js.org/).


## Node API

```js
const {run, report} = require('c8')

run(argv, async () => {
// run your code
}).then(reports => report({
reports,
reporter: ['text', 'lcov']
}))
```
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict'

exports.run = require('./lib/run')
exports.report = require('./lib/report')
5 changes: 4 additions & 1 deletion lib/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const {readdirSync, readFileSync} = require('fs')
const {resolve} = require('path')

class Report {
constructor ({reporter, coverageDirectory, watermarks}) {
constructor ({reporter, reports, coverageDirectory, watermarks}) {
this.reporter = reporter
this.reports = reports
this.coverageDirectory = coverageDirectory
this.watermarks = watermarks
}
Expand All @@ -33,6 +34,8 @@ class Report {
return map
}
_loadReports () {
if (this.reports) return this.reports

const tmpDirctory = resolve(this.coverageDirectory, './tmp')
const files = readdirSync(tmpDirctory)

Expand Down
75 changes: 75 additions & 0 deletions lib/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use strict'

const Exclude = require('test-exclude')
const inspector = require('inspector')
const { isAbsolute } = require('path')
const v8ToIstanbul = require('v8-to-istanbul')

module.exports = async function (argv, cb) {
const exclude = Exclude({
include: argv.include,
exclude: argv.exclude
})

const session = bootstrap()

const done = () => {
return onExit(session, { exclude, argv })
}

const cbResult = cb()
if (cbResult.then) {
return cbResult.then(done)
} else {
throw new Error('Callback needs to return a promise')
}
}

function bootstrap () {
// bootstrap the inspector before kicking
// off the user's code.
inspector.open(0, true)
const session = new inspector.Session()
session.connect()

session.post('Profiler.enable')
session.post('Runtime.enable')
session.post(
'Profiler.startPreciseCoverage', { callCount: true, detailed: true }
)
return session
}

function onExit (session, opts) {
return new Promise((resolve, reject) => {
session.post('Profiler.takePreciseCoverage', (err, res) => {
if (err) reject(err)
else {
try {
const result = getIstanbulFormatCoverage(filterResult(res.result, opts))
resolve(result)
} catch (err) {
reject(err)
}
}
})
})
}

function filterResult (result, { exclude }) {
result = result.filter(({ url }) => {
url = url.replace('file://', '')
return isAbsolute(url) &&
exclude.shouldInstrument(url) &&
url !== __filename
})
return result
}

function getIstanbulFormatCoverage (allV8Coverage) {
return allV8Coverage.map((v8) => {
const script = v8ToIstanbul(v8.url)
script.applyCoverage(v8.functions)
return script.toIstanbul()
})
}
75 changes: 13 additions & 62 deletions lib/wrap.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,31 @@
#!/usr/bin/env node
'use strict'

const Exclude = require('test-exclude')
const inspector = require('inspector')
const {isAbsolute} = require('path')
const { writeFileSync } = require('fs')
const { resolve } = require('path')
const onExit = require('signal-exit')
const {resolve} = require('path')
const sw = require('spawn-wrap')
const uuid = require('uuid')
const v8ToIstanbul = require('v8-to-istanbul')
const {writeFileSync} = require('fs')
const run = require('./run')

const argv = JSON.parse(process.env.C8_ARGV)

const exclude = Exclude({
include: argv.include,
exclude: argv.exclude
})
wrap()

;(async function runInstrumented () {
try {
// bootstrap the inspector before kicking
// off the user's code.
inspector.open(0, true)
const session = new inspector.Session()
session.connect()

session.post('Profiler.enable')
session.post('Runtime.enable')
session.post(
'Profiler.startPreciseCoverage',
{callCount: true, detailed: true}
)

// hook process.exit() and common exit signals, e.g., SIGTERM,
// and output coverage report when these occur.
onExit(() => {
session.post('Profiler.takePreciseCoverage', (err, res) => {
if (err) console.warn(err.message)
else {
try {
const result = filterResult(res.result)
writeIstanbulFormatCoverage(result)
} catch (err) {
console.warn(err.message)
}
}
})
}, {alwaysLast: true})

// run the user's actual application.
async function wrap () {
const result = await run(argv, async () => {
sw.runMain()
} catch (err) {
console.error(err)
process.exit(1)
}
})()

function filterResult (result) {
result = result.filter(({url}) => {
url = url.replace('file://', '')
return isAbsolute(url) &&
exclude.shouldInstrument(url) &&
url !== __filename
return new Promise((resolve) => onExit(resolve, { alwaysLast: true }))
})
return result
write(result, { argv })
}

function writeIstanbulFormatCoverage (allV8Coverage) {
const tmpDirctory = resolve(argv.coverageDirectory, './tmp')
allV8Coverage.forEach((v8) => {
const script = v8ToIstanbul(v8.url)
script.applyCoverage(v8.functions)
function write (coverage, { argv }) {
const tmpDirectory = resolve(argv.coverageDirectory, './tmp')
coverage.forEach((istanbul) => {
writeFileSync(
resolve(tmpDirctory, `./${uuid.v4()}.json`),
JSON.stringify(script.toIstanbul(), null, 2),
resolve(tmpDirectory, `./${uuid.v4()}.json`),
JSON.stringify(istanbul, null, 2),
'utf8'
)
})
Expand Down
17 changes: 10 additions & 7 deletions test/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ describe('c8', () => {
cwd: process.cwd()
})
output.toString('utf8').should.include(`
------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
------------|----------|----------|----------|----------|----------------|
All files | 91.67 | 100 | 80 | 91.67 | |
normal.js | 85.71 | 100 | 50 | 85.71 | 14,15,16 |
timeout.js | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|----------------|`)
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 47.14 | 80 | 0 | 47.14 | |
lib | 0 | 100 | 100 | 0 | |
wrap.js | 0 | 100 | 100 | 0 |... 31,32,33,34 |
test/fixtures | 91.67 | 80 | 0 | 91.67 | |
normal.js | 85.71 | 75 | 0 | 85.71 | 14,15,16 |
timeout.js | 100 | 83.33 | 100 | 100 | 1 |
---------------|----------|----------|----------|----------|----------------|`)
})
})