Skip to content
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

feat: adds instrument command line option #298

Merged
merged 4 commits into from
Jul 7, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
50 changes: 36 additions & 14 deletions bin/nyc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,47 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
.command('report', 'run coverage report for .nyc_output', function (yargs) {
return yargs
.usage('$0 report [options]')
.option('reporter', {
alias: 'r',
describe: 'coverage reporter(s) to use',
default: 'text'
})
.option('report-dir', {
describe: 'default directory to output coverage reports in',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this simply say:

directory to output coverage reports in

The default is coverage. If report-dir is specified, it's no longer the "default".

default: 'coverage'
})
.example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage')
})
.command('check-coverage', 'check whether coverage is within thresholds provided', function (yargs) {
return yargs
.usage('$0 check-coverage [options]')
.option('branches', {
default: 0,
description: 'what % of branches must be covered?'
})
.option('functions', {
default: 0,
description: 'what % of functions must be covered?'
})
.option('lines', {
default: 90,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Is there a reason this is 90 instead of 0?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just picking defaults based on personal preference, I tend to find a project with 90%+ test coverage is pretty healthy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Maybe I'm misunderstanding what these numbers mean, but shouldn't that be true of the other thresholds as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, just not as experienced with the other thresholds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, me either. I only think that I sort of understand what they really mean, but I'm not certain...

description: 'what % of lines must be covered?'
})
.option('statements', {
default: 0,
description: 'what % of statements must be covered?'
})
.example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided")
})
.command(require('../lib/commands/instrument'))
.option('reporter', {
alias: 'r',
describe: 'coverage reporter(s) to use',
default: 'text',
global: true
default: 'text'
})
.option('report-dir', {
describe: 'default directory to output coverage reports in',
default: 'coverage',
global: true
default: 'coverage'
})
.option('silent', {
alias: 's',
Expand Down Expand Up @@ -67,7 +91,7 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
type: 'boolean',
describe: 'cache instrumentation results for improved performance'
})
.options('extension', {
.option('extension', {
alias: 'e',
default: [],
describe: 'a list of extensions that nyc should handle in addition to .js'
Expand All @@ -79,23 +103,19 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
})
.option('branches', {
default: 0,
description: 'what % of branches must be covered?',
global: true
description: 'what % of branches must be covered?'
})
.option('functions', {
default: 0,
description: 'what % of functions must be covered?',
global: true
description: 'what % of functions must be covered?'
})
.option('lines', {
default: 90,
description: 'what % of lines must be covered?',
global: true
description: 'what % of lines must be covered?'
})
.option('statements', {
default: 0,
description: 'what % of statements must be covered?',
global: true
description: 'what % of statements must be covered?'
})
.option('source-map', {
default: true,
Expand All @@ -112,7 +132,7 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
.version()
.pkgConf('nyc', process.cwd())
.example('$0 npm test', 'instrument your tests with coverage')
.example('$0 --require --require babel-core/register npm test', 'instrument your tests with coverage and babel')
.example('$0 --require babel-core/register npm test', 'instrument your tests with coverage and babel')
.example('$0 report --reporter=text-lcov', 'output lcov report after running your tests')
.epilog('visit https://git.io/voHar for list of available reporters')

Expand All @@ -125,6 +145,8 @@ if (argv._[0] === 'report') {
report(argv)
} else if (argv._[0] === 'check-coverage') {
checkCoverage(argv)
} else if (argv._[0] === 'instrument') {
// noop, let the command handler do its thing.
} else if (argv._.length) {
// wrap subprocesses and execute argv[1]
argv.require = arrify(argv.require)
Expand Down
67 changes: 58 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,10 @@ NYC.prototype.addAllFiles = function () {

this._loadAdditionalModules()

var pattern = null
if (this.extensions.length === 1) {
pattern = '**/*' + this.extensions[0]
} else {
pattern = '**/*{' + this.extensions.join() + '}'
}

this.fakeRequire = true
glob.sync(pattern, {cwd: this.cwd, nodir: true, ignore: this.exclude.exclude}).forEach(function (filename) {
this.walkAllFiles(this.cwd, function (filename) {
filename = path.resolve(_this.cwd, filename)
_this.addFile(filename)

var coverage = coverageFinder()
var lastCoverage = _this.instrumenter().lastFileCoverage()
if (lastCoverage && _this.exclude.shouldInstrument(filename)) {
Expand All @@ -180,6 +172,63 @@ NYC.prototype.addAllFiles = function () {
this.writeCoverageFile()
}

NYC.prototype.instrumentAllFiles = function (input, output, cb) {
var _this = this
var inputDir = '.' + path.sep
var visitor = function (filename) {
var ext
var transform
var inFile = path.relative(_this.cwd, path.resolve(inputDir, filename))
var code = fs.readFileSync(inFile, 'utf-8')

for (ext in _this.transforms) {
if (filename.toLowerCase().substr(-ext.length) === ext) {
transform = _this.transforms[ext]
break
}
}

if (transform) {
code = transform(code, {filename: filename, relFile: inFile})
}

if (!output) {
console.log(code)
} else {
var outFile = path.relative(_this.cwd, path.resolve(output, filename))
mkdirp.sync(path.dirname(outFile))
fs.writeFileSync(outFile, code, 'utf-8')
}
}

this._loadAdditionalModules()

try {
var stats = fs.lstatSync(input)
if (stats.isDirectory()) {
inputDir = input
this.walkAllFiles(input, visitor)
} else {
visitor(input)
}
} catch (err) {
return cb(err)
}
}

NYC.prototype.walkAllFiles = function (dir, visitor) {
var pattern = null
if (this.extensions.length === 1) {
pattern = '**/*' + this.extensions[0]
} else {
pattern = '**/*{' + this.extensions.join() + '}'
}

glob.sync(pattern, {cwd: dir, nodir: true, ignore: this.exclude.exclude}).forEach(function (filename) {
visitor(filename)
})
}

NYC.prototype._maybeInstrumentSource = function (code, filename, relFile) {
var instrument = this.exclude.shouldInstrument(filename, relFile)
if (!instrument) {
Expand Down
50 changes: 50 additions & 0 deletions lib/commands/instrument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var NYC = require('../../')

exports.command = 'instrument <input> [output]'

exports.describe = 'instruments a file or a directory tree and writes the instrumented code to the desired output location'

exports.builder = function (yargs) {
return yargs
.usage('$0 instrument <input> [output]')
.option('require', {
alias: 'i',
default: [],
describe: 'a list of additional modules that nyc should attempt to require in its subprocess, e.g., babel-register, babel-polyfill.'
})
.option('extension', {
alias: 'e',
default: [],
describe: 'a list of extensions that nyc should handle in addition to .js'
})
.option('source-map', {
default: true,
type: 'boolean',
description: 'should nyc detect and handle source maps?'
})
.option('instrument', {
default: true,
type: 'boolean',
description: 'should nyc handle instrumentation?'
})
.example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output')
}

exports.handler = function (argv) {
// if instrument is set to false,
// enable a noop instrumenter.
if (!argv.instrument) argv.instrumenter = './lib/instrumenters/noop'
else argv.instrumenter = './lib/instrumenters/istanbul'

var nyc = new NYC({
instrumenter: argv.instrumenter,
sourceMap: argv.sourceMap,
extension: argv.extension,
require: argv.require
})

nyc.instrumentAllFiles(argv.input, argv.output, function (err) {
if (err) console.error(err.message)
process.exit(1)
})
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@
"istanbul-lib-instrument": "^1.1.0-alpha.1",
"istanbul-lib-report": "^1.0.0-alpha.3",
"istanbul-lib-source-maps": "^1.0.0-alpha.10",
"istanbul-reports": "^1.0.0-alpha.6",
"istanbul-reports": "^1.0.0-alpha.7",
"md5-hex": "^1.2.0",
"micromatch": "^2.3.7",
"mkdirp": "^0.5.0",
"pkg-up": "^1.0.0",
"resolve-from": "^2.0.0",
"rimraf": "^2.5.0",
"rimraf": "^2.5.3",
"signal-exit": "^3.0.0",
"spawn-wrap": "^1.2.2",
"test-exclude": "^1.1.0",
Expand Down Expand Up @@ -147,4 +147,4 @@
"test-exclude",
"yargs"
]
}
}
94 changes: 94 additions & 0 deletions test/src/nyc-bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,98 @@ describe('the nyc cli', function () {
})
})
})

describe('instrument', function () {
describe('no output folder', function () {
it('allows a single file to be instrumented', function (done) {
var args = [bin, 'instrument', './half-covered.js']

var proc = spawn(process.execPath, args, {
cwd: fixturesCLI,
env: env
})

var stdout = ''
proc.stdout.on('data', function (chunk) {
stdout += chunk
})

proc.on('close', function (code) {
code.should.equal(0)
stdout.should.match(/path:"\.\/half-covered\.js"/)
done()
})
})

it('allows a directory of files to be instrumented', function (done) {
var args = [bin, 'instrument', './']

var proc = spawn(process.execPath, args, {
cwd: fixturesCLI,
env: env
})

var stdout = ''
proc.stdout.on('data', function (chunk) {
stdout += chunk
})

proc.on('close', function (code) {
code.should.equal(0)
stdout.should.match(/half-covered\.js"/)
stdout.should.match(/half-covered-failing\.js"/)
stdout.should.not.match(/spawn\.js"/)
done()
})
})
})

describe('output folder specified', function () {
it('allows a single file to be instrumented', function (done) {
var args = [bin, 'instrument', './half-covered.js', './output']

var proc = spawn(process.execPath, args, {
cwd: fixturesCLI,
env: env
})

var stdout = ''
proc.stdout.on('data', function (chunk) {
stdout += chunk
})

proc.on('close', function (code) {
code.should.equal(0)
var files = fs.readdirSync(path.resolve(fixturesCLI, './output'))
files.length.should.equal(1)
files.should.include('half-covered.js')
rimraf.sync(path.resolve(fixturesCLI, 'output'))
done()
})
})

it('allows a directory of files to be instrumented', function (done) {
var args = [bin, 'instrument', './', './output']

var proc = spawn(process.execPath, args, {
cwd: fixturesCLI,
env: env
})

var stdout = ''
proc.stdout.on('data', function (chunk) {
stdout += chunk
})

proc.on('close', function (code) {
code.should.equal(0)
var files = fs.readdirSync(path.resolve(fixturesCLI, './output'))
files.should.include('env.js')
files.should.include('es6.js')
rimraf.sync(path.resolve(fixturesCLI, 'output'))
done()
})
})
})
})
})