From f854b7d7ff36d0d3c855efde55f4520f8b3b023e Mon Sep 17 00:00:00 2001 From: laggingreflex Date: Sat, 4 Aug 2018 20:59:18 +0530 Subject: [PATCH] feat: Node API This commit attempts to refactor the code to separate the running/reporting/writing logic so that it can be consumed by other libraries --- README.md | 16 +++++++++- index.js | 4 +++ lib/report.js | 5 ++- lib/run.js | 75 +++++++++++++++++++++++++++++++++++++++++++++ lib/wrap.js | 75 ++++++++------------------------------------- test/integration.js | 17 +++++----- 6 files changed, 121 insertions(+), 71 deletions(-) create mode 100644 index.js create mode 100644 lib/run.js diff --git a/README.md b/README.md index db2f2470..e87fabc8 100644 --- a/README.md +++ b/README.md @@ -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/). \ No newline at end of file +[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'] +})) +``` diff --git a/index.js b/index.js new file mode 100644 index 00000000..d7bdb54e --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +'use strict' + +exports.run = require('./lib/run') +exports.report = require('./lib/report') diff --git a/lib/report.js b/lib/report.js index 04592d6b..bd3d7d40 100644 --- a/lib/report.js +++ b/lib/report.js @@ -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 } @@ -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) diff --git a/lib/run.js b/lib/run.js new file mode 100644 index 00000000..d3b56feb --- /dev/null +++ b/lib/run.js @@ -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() + }) +} diff --git a/lib/wrap.js b/lib/wrap.js index c80b8bff..bc2a42d0 100644 --- a/lib/wrap.js +++ b/lib/wrap.js @@ -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' ) }) diff --git a/test/integration.js b/test/integration.js index e543c0e3..c077d44b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -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 | +---------------|----------|----------|----------|----------|----------------|`) }) })