diff options
Diffstat (limited to 'node_modules/istanbul/lib/command')
-rw-r--r-- | node_modules/istanbul/lib/command/check-coverage.js | 195 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/common/run-with-cover.js | 261 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/cover.js | 33 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/help.js | 102 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/index.js | 33 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/instrument.js | 265 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/report.js | 123 | ||||
-rw-r--r-- | node_modules/istanbul/lib/command/test.js | 31 |
8 files changed, 1043 insertions, 0 deletions
diff --git a/node_modules/istanbul/lib/command/check-coverage.js b/node_modules/istanbul/lib/command/check-coverage.js new file mode 100644 index 000000000..5776c7780 --- /dev/null +++ b/node_modules/istanbul/lib/command/check-coverage.js @@ -0,0 +1,195 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var nopt = require('nopt'), + path = require('path'), + fs = require('fs'), + Collector = require('../collector'), + formatOption = require('../util/help-formatter').formatOption, + util = require('util'), + utils = require('../object-utils'), + filesFor = require('../util/file-matcher').filesFor, + Command = require('./index'), + configuration = require('../config'); + +function isAbsolute(file) { + if (path.isAbsolute) { + return path.isAbsolute(file); + } + + return path.resolve(file) === path.normalize(file); +} + +function CheckCoverageCommand() { + Command.call(this); +} + +function removeFiles(covObj, root, files) { + var filesObj = {}, + obj = {}; + + // Create lookup table. + files.forEach(function (file) { + filesObj[file] = true; + }); + + Object.keys(covObj).forEach(function (key) { + // Exclude keys will always be relative, but covObj keys can be absolute or relative + var excludeKey = isAbsolute(key) ? path.relative(root, key) : key; + // Also normalize for files that start with `./`, etc. + excludeKey = path.normalize(excludeKey); + if (filesObj[excludeKey] !== true) { + obj[key] = covObj[key]; + } + }); + + return obj; +} + +CheckCoverageCommand.TYPE = 'check-coverage'; +util.inherits(CheckCoverageCommand, Command); + +Command.mix(CheckCoverageCommand, { + synopsis: function () { + return "checks overall/per-file coverage against thresholds from coverage JSON files. Exits 1 if thresholds are not met, 0 otherwise"; + }, + + usage: function () { + console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [<include-pattern>]\n\nOptions are:\n\n' + + [ + formatOption('--statements <threshold>', 'global statement coverage threshold'), + formatOption('--functions <threshold>', 'global function coverage threshold'), + formatOption('--branches <threshold>', 'global branch coverage threshold'), + formatOption('--lines <threshold>', 'global line coverage threshold') + ].join('\n\n') + '\n'); + + console.error('\n\n'); + + console.error('Thresholds, when specified as a positive number are taken to be the minimum percentage required.'); + console.error('When a threshold is specified as a negative number it represents the maximum number of uncovered entities allowed.\n'); + console.error('For example, --statements 90 implies minimum statement coverage is 90%.'); + console.error(' --statements -10 implies that no more than 10 uncovered statements are allowed\n'); + console.error('Per-file thresholds can be specified via a configuration file.\n'); + console.error('<include-pattern> is a glob pattern that can be used to select one or more coverage files ' + + 'for merge. This defaults to "**/coverage*.json"'); + + console.error('\n'); + }, + + run: function (args, callback) { + + var template = { + config: path, + root: path, + statements: Number, + lines: Number, + branches: Number, + functions: Number, + verbose: Boolean + }, + opts = nopt(template, { v : '--verbose' }, args, 0), + // Translate to config opts. + config = configuration.loadFile(opts.config, { + verbose: opts.verbose, + check: { + global: { + statements: opts.statements, + lines: opts.lines, + branches: opts.branches, + functions: opts.functions + } + } + }), + includePattern = '**/coverage*.json', + root, + collector = new Collector(), + errors = []; + + if (opts.argv.remain.length > 0) { + includePattern = opts.argv.remain[0]; + } + + root = opts.root || process.cwd(); + filesFor({ + root: root, + includes: [ includePattern ] + }, function (err, files) { + if (err) { throw err; } + if (files.length === 0) { + return callback('ERROR: No coverage files found.'); + } + files.forEach(function (file) { + var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8')); + collector.add(coverageObject); + }); + var thresholds = { + global: { + statements: config.check.global.statements || 0, + branches: config.check.global.branches || 0, + lines: config.check.global.lines || 0, + functions: config.check.global.functions || 0, + excludes: config.check.global.excludes || [] + }, + each: { + statements: config.check.each.statements || 0, + branches: config.check.each.branches || 0, + lines: config.check.each.lines || 0, + functions: config.check.each.functions || 0, + excludes: config.check.each.excludes || [] + } + }, + rawCoverage = collector.getFinalCoverage(), + globalResults = utils.summarizeCoverage(removeFiles(rawCoverage, root, thresholds.global.excludes)), + eachResults = removeFiles(rawCoverage, root, thresholds.each.excludes); + + // Summarize per-file results and mutate original results. + Object.keys(eachResults).forEach(function (key) { + eachResults[key] = utils.summarizeFileCoverage(eachResults[key]); + }); + + if (config.verbose) { + console.log('Compare actuals against thresholds'); + console.log(JSON.stringify({ global: globalResults, each: eachResults, thresholds: thresholds }, undefined, 4)); + } + + function check(name, thresholds, actuals) { + [ + "statements", + "branches", + "lines", + "functions" + ].forEach(function (key) { + var actual = actuals[key].pct, + actualUncovered = actuals[key].total - actuals[key].covered, + threshold = thresholds[key]; + + if (threshold < 0) { + if (threshold * -1 < actualUncovered) { + errors.push('ERROR: Uncovered count for ' + key + ' (' + actualUncovered + + ') exceeds ' + name + ' threshold (' + -1 * threshold + ')'); + } + } else { + if (actual < threshold) { + errors.push('ERROR: Coverage for ' + key + ' (' + actual + + '%) does not meet ' + name + ' threshold (' + threshold + '%)'); + } + } + }); + } + + check("global", thresholds.global, globalResults); + + Object.keys(eachResults).forEach(function (key) { + check("per-file" + " (" + key + ") ", thresholds.each, eachResults[key]); + }); + + return callback(errors.length === 0 ? null : errors.join("\n")); + }); + } +}); + +module.exports = CheckCoverageCommand; + + diff --git a/node_modules/istanbul/lib/command/common/run-with-cover.js b/node_modules/istanbul/lib/command/common/run-with-cover.js new file mode 100644 index 000000000..d4c5fafe4 --- /dev/null +++ b/node_modules/istanbul/lib/command/common/run-with-cover.js @@ -0,0 +1,261 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +var Module = require('module'), + path = require('path'), + fs = require('fs'), + nopt = require('nopt'), + which = require('which'), + mkdirp = require('mkdirp'), + existsSync = fs.existsSync || path.existsSync, + inputError = require('../../util/input-error'), + matcherFor = require('../../util/file-matcher').matcherFor, + Instrumenter = require('../../instrumenter'), + Collector = require('../../collector'), + formatOption = require('../../util/help-formatter').formatOption, + hook = require('../../hook'), + Reporter = require('../../reporter'), + resolve = require('resolve'), + configuration = require('../../config'); + +function usage(arg0, command) { + + console.error('\nUsage: ' + arg0 + ' ' + command + ' [<options>] <executable-js-file-or-command> [-- <arguments-to-jsfile>]\n\nOptions are:\n\n' + + [ + formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'), + formatOption('--root <path> ', 'the root path to look for files to instrument, defaults to .'), + formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more glob patterns e.g. "**/vendor/**"'), + formatOption('-i <include-pattern> [-i <include-pattern>]', 'one or more glob patterns e.g. "**/*.js"'), + formatOption('--[no-]default-excludes', 'apply default excludes [ **/node_modules/**, **/test/**, **/tests/** ], defaults to true'), + formatOption('--hook-run-in-context', 'hook vm.runInThisContext in addition to require (supports RequireJS), defaults to false'), + formatOption('--post-require-hook <file> | <module>', 'JS module that exports a function for post-require processing'), + formatOption('--report <format> [--report <format>] ', 'report format, defaults to lcov (= lcov.info + HTML)'), + formatOption('--dir <report-dir>', 'report directory, defaults to ./coverage'), + formatOption('--print <type>', 'type of report to print to console, one of summary (default), detail, both or none'), + formatOption('--verbose, -v', 'verbose mode'), + formatOption('--[no-]preserve-comments', 'remove / preserve comments in the output, defaults to false'), + formatOption('--include-all-sources', 'instrument all unused sources after running tests, defaults to false'), + formatOption('--[no-]include-pid', 'include PID in output coverage filename') + ].join('\n\n') + '\n'); + console.error('\n'); +} + +function run(args, commandName, enableHooks, callback) { + + var template = { + config: path, + root: path, + x: [ Array, String ], + report: [Array, String ], + dir: path, + verbose: Boolean, + yui: Boolean, + 'default-excludes': Boolean, + print: String, + 'self-test': Boolean, + 'hook-run-in-context': Boolean, + 'post-require-hook': String, + 'preserve-comments': Boolean, + 'include-all-sources': Boolean, + 'preload-sources': Boolean, + i: [ Array, String ], + 'include-pid': Boolean + }, + opts = nopt(template, { v : '--verbose' }, args, 0), + overrides = { + verbose: opts.verbose, + instrumentation: { + root: opts.root, + 'default-excludes': opts['default-excludes'], + excludes: opts.x, + 'include-all-sources': opts['include-all-sources'], + 'preload-sources': opts['preload-sources'], + 'include-pid': opts['include-pid'] + }, + reporting: { + reports: opts.report, + print: opts.print, + dir: opts.dir + }, + hooks: { + 'hook-run-in-context': opts['hook-run-in-context'], + 'post-require-hook': opts['post-require-hook'], + 'handle-sigint': opts['handle-sigint'] + } + }, + config = configuration.loadFile(opts.config, overrides), + verbose = config.verbose, + cmdAndArgs = opts.argv.remain, + preserveComments = opts['preserve-comments'], + includePid = opts['include-pid'], + cmd, + cmdArgs, + reportingDir, + reporter = new Reporter(config), + runFn, + excludes; + + if (cmdAndArgs.length === 0) { + return callback(inputError.create('Need a filename argument for the ' + commandName + ' command!')); + } + + cmd = cmdAndArgs.shift(); + cmdArgs = cmdAndArgs; + + if (!existsSync(cmd)) { + try { + cmd = which.sync(cmd); + } catch (ex) { + return callback(inputError.create('Unable to resolve file [' + cmd + ']')); + } + } else { + cmd = path.resolve(cmd); + } + + runFn = function () { + process.argv = ["node", cmd].concat(cmdArgs); + if (verbose) { + console.log('Running: ' + process.argv.join(' ')); + } + process.env.running_under_istanbul=1; + Module.runMain(cmd, null, true); + }; + + excludes = config.instrumentation.excludes(true); + + if (enableHooks) { + reportingDir = path.resolve(config.reporting.dir()); + mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this + reporter.dir = reportingDir; + reporter.addAll(config.reporting.reports()); + if (config.reporting.print() !== 'none') { + switch (config.reporting.print()) { + case 'detail': + reporter.add('text'); + break; + case 'both': + reporter.add('text'); + reporter.add('text-summary'); + break; + default: + reporter.add('text-summary'); + break; + } + } + + excludes.push(path.relative(process.cwd(), path.join(reportingDir, '**', '*'))); + matcherFor({ + root: config.instrumentation.root() || process.cwd(), + includes: opts.i || config.instrumentation.extensions().map(function (ext) { + return '**/*' + ext; + }), + excludes: excludes + }, + function (err, matchFn) { + if (err) { return callback(err); } + + var coverageVar = '$$cov_' + new Date().getTime() + '$$', + instrumenter = new Instrumenter({ coverageVariable: coverageVar , preserveComments: preserveComments}), + transformer = instrumenter.instrumentSync.bind(instrumenter), + hookOpts = { verbose: verbose, extensions: config.instrumentation.extensions() }, + postRequireHook = config.hooks.postRequireHook(), + postLoadHookFile; + + if (postRequireHook) { + postLoadHookFile = path.resolve(postRequireHook); + } else if (opts.yui) { //EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed + postLoadHookFile = path.resolve(__dirname, '../../util/yui-load-hook'); + } + + if (postRequireHook) { + if (!existsSync(postLoadHookFile)) { //assume it is a module name and resolve it + try { + postLoadHookFile = resolve.sync(postRequireHook, { basedir: process.cwd() }); + } catch (ex) { + if (verbose) { console.error('Unable to resolve [' + postRequireHook + '] as a node module'); } + callback(ex); + return; + } + } + } + if (postLoadHookFile) { + if (verbose) { console.error('Use post-load-hook: ' + postLoadHookFile); } + hookOpts.postLoadHook = require(postLoadHookFile)(matchFn, transformer, verbose); + } + + if (opts['self-test']) { + hook.unloadRequireCache(matchFn); + } + // runInThisContext is used by RequireJS [issue #23] + if (config.hooks.hookRunInContext()) { + hook.hookRunInThisContext(matchFn, transformer, hookOpts); + } + hook.hookRequire(matchFn, transformer, hookOpts); + + //initialize the global variable to stop mocha from complaining about leaks + global[coverageVar] = {}; + + // enable passing --handle-sigint to write reports on SIGINT. + // This allows a user to manually kill a process while + // still getting the istanbul report. + if (config.hooks.handleSigint()) { + process.once('SIGINT', process.exit); + } + + process.once('exit', function () { + var pidExt = includePid ? ('-' + process.pid) : '', + file = path.resolve(reportingDir, 'coverage' + pidExt + '.json'), + collector, + cov; + if (typeof global[coverageVar] === 'undefined' || Object.keys(global[coverageVar]).length === 0) { + console.error('No coverage information was collected, exit without writing coverage information'); + return; + } else { + cov = global[coverageVar]; + } + //important: there is no event loop at this point + //everything that happens in this exit handler MUST be synchronous + if (config.instrumentation.includeAllSources()) { + // Files that are not touched by code ran by the test runner is manually instrumented, to + // illustrate the missing coverage. + matchFn.files.forEach(function (file) { + if (!cov[file]) { + transformer(fs.readFileSync(file, 'utf-8'), file); + + // When instrumenting the code, istanbul will give each FunctionDeclaration a value of 1 in coverState.s, + // presumably to compensate for function hoisting. We need to reset this, as the function was not hoisted, + // as it was never loaded. + Object.keys(instrumenter.coverState.s).forEach(function (key) { + instrumenter.coverState.s[key] = 0; + }); + + cov[file] = instrumenter.coverState; + } + }); + } + mkdirp.sync(reportingDir); //yes, do this again since some test runners could clean the dir initially created + if (config.reporting.print() !== 'none') { + console.error('============================================================================='); + console.error('Writing coverage object [' + file + ']'); + } + fs.writeFileSync(file, JSON.stringify(cov), 'utf8'); + collector = new Collector(); + collector.add(cov); + if (config.reporting.print() !== 'none') { + console.error('Writing coverage reports at [' + reportingDir + ']'); + console.error('============================================================================='); + } + reporter.write(collector, true, callback); + }); + runFn(); + }); + } else { + runFn(); + } +} + +module.exports = { + run: run, + usage: usage +}; diff --git a/node_modules/istanbul/lib/command/cover.js b/node_modules/istanbul/lib/command/cover.js new file mode 100644 index 000000000..ee8242917 --- /dev/null +++ b/node_modules/istanbul/lib/command/cover.js @@ -0,0 +1,33 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var runWithCover = require('./common/run-with-cover'), + util = require('util'), + Command = require('./index'); + +function CoverCommand() { + Command.call(this); +} + +CoverCommand.TYPE = 'cover'; +util.inherits(CoverCommand, Command); + +Command.mix(CoverCommand, { + synopsis: function () { + return "transparently adds coverage information to a node command. Saves coverage.json and reports at the end of execution"; + }, + + usage: function () { + runWithCover.usage(this.toolName(), this.type()); + }, + + run: function (args, callback) { + runWithCover.run(args, this.type(), true, callback); + } +}); + + +module.exports = CoverCommand; + diff --git a/node_modules/istanbul/lib/command/help.js b/node_modules/istanbul/lib/command/help.js new file mode 100644 index 000000000..e3f6d76b7 --- /dev/null +++ b/node_modules/istanbul/lib/command/help.js @@ -0,0 +1,102 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var Command = require('./index.js'), + util = require('util'), + formatOption = require('../util/help-formatter').formatOption, + VERSION = require('../../index').VERSION, + configuration = require('../config'), + yaml = require('js-yaml'), + formatPara = require('../util/help-formatter').formatPara; + +function showConfigHelp(toolName) { + + console.error('\nConfiguring ' + toolName); + console.error('===================='); + console.error('\n' + + formatPara(toolName + ' can be configured globally using a .istanbul.yml YAML file ' + + 'at the root of your source tree. Every command also accepts a --config=<config-file> argument to ' + + 'customize its location per command. The alternate config file can be in YAML, JSON or node.js ' + + '(exporting the config object).')); + console.error('\n' + + formatPara('The config file currently has four sections for instrumentation, reporting, hooks, ' + + 'and checking. Note that certain commands (like `cover`) use information from multiple sections.')); + console.error('\n' + + formatPara('Keys in the config file usually correspond to command line parameters with the same name. ' + + 'The verbose option for every command shows you the exact configuration used. See the api ' + + 'docs for an explanation of each key.')); + + console.error('\n' + + formatPara('You only need to specify the keys that you want to override. Your overrides will be merged ' + + 'with the default config.')); + console.error('\nThe default configuration is as follows:\n'); + console.error(yaml.safeDump(configuration.defaultConfig(), { indent: 4, flowLevel: 3 })); + console.error('\n' + + formatPara('The `watermarks` section does not have a command line equivalent. It allows you to set up ' + + 'low and high watermark percentages for reporting. These are honored by all reporters that colorize ' + + 'their output based on low/ medium/ high coverage.')); + console.error('\n' + + formatPara('The `reportConfig` section allows you to configure each report format independently ' + + 'and has no command-line equivalent either.')); + console.error('\n' + + formatPara('The `check` section configures minimum threshold enforcement for coverage results. ' + + '`global` applies to all files together and `each` on a per-file basis. A list of files can ' + + 'be excluded from enforcement relative to root via the `exclude` property.')); + console.error(''); +} + +function HelpCommand() { + Command.call(this); +} + +HelpCommand.TYPE = 'help'; +util.inherits(HelpCommand, Command); + +Command.mix(HelpCommand, { + synopsis: function () { + return "shows help"; + }, + + usage: function () { + + console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' config | <command>\n'); + console.error('`config` provides help with istanbul configuration\n'); + console.error('Available commands are:\n'); + + var commandObj; + Command.getCommandList().forEach(function (cmd) { + commandObj = Command.create(cmd); + console.error(formatOption(cmd, commandObj.synopsis())); + console.error("\n"); + }); + console.error("Command names can be abbreviated as long as the abbreviation is unambiguous"); + console.error(this.toolName() + ' version:' + VERSION); + console.error("\n"); + }, + run: function (args, callback) { + var command; + if (args.length === 0) { + this.usage(); + } else { + if (args[0] === 'config') { + showConfigHelp(this.toolName()); + } else { + try { + command = Command.create(args[0]); + command.usage('istanbul', Command.resolveCommandName(args[0])); + } catch (ex) { + console.error('Invalid command: ' + args[0]); + this.usage(); + } + } + } + return callback(); + } +}); + + +module.exports = HelpCommand; + + diff --git a/node_modules/istanbul/lib/command/index.js b/node_modules/istanbul/lib/command/index.js new file mode 100644 index 000000000..754cf1d0d --- /dev/null +++ b/node_modules/istanbul/lib/command/index.js @@ -0,0 +1,33 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var Factory = require('../util/factory'), + factory = new Factory('command', __dirname, true); + +function Command() {} +// add register, create, mix, loadAll, getCommandList, resolveCommandName to the Command object +factory.bindClassMethods(Command); + +Command.prototype = { + toolName: function () { + return require('../util/meta').NAME; + }, + + type: function () { + return this.constructor.TYPE; + }, + synopsis: /* istanbul ignore next: base method */ function () { + return "the developer has not written a one-line summary of the " + this.type() + " command"; + }, + usage: /* istanbul ignore next: base method */ function () { + console.error("the developer has not provided a usage for the " + this.type() + " command"); + }, + run: /* istanbul ignore next: abstract method */ function (args, callback) { + return callback(new Error("run: must be overridden for the " + this.type() + " command")); + } +}; + +module.exports = Command; + diff --git a/node_modules/istanbul/lib/command/instrument.js b/node_modules/istanbul/lib/command/instrument.js new file mode 100644 index 000000000..d08d6b87d --- /dev/null +++ b/node_modules/istanbul/lib/command/instrument.js @@ -0,0 +1,265 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var path = require('path'), + mkdirp = require('mkdirp'), + once = require('once'), + async = require('async'), + fs = require('fs'), + filesFor = require('../util/file-matcher').filesFor, + nopt = require('nopt'), + Instrumenter = require('../instrumenter'), + inputError = require('../util/input-error'), + formatOption = require('../util/help-formatter').formatOption, + util = require('util'), + Command = require('./index'), + Collector = require('../collector'), + configuration = require('../config'), + verbose; + + +/* + * Chunk file size to use when reading non JavaScript files in memory + * and copying them over when using complete-copy flag. + */ +var READ_FILE_CHUNK_SIZE = 64 * 1024; + +function BaselineCollector(instrumenter) { + this.instrumenter = instrumenter; + this.collector = new Collector(); + this.instrument = instrumenter.instrument.bind(this.instrumenter); + + var origInstrumentSync = instrumenter.instrumentSync; + this.instrumentSync = function () { + var args = Array.prototype.slice.call(arguments), + ret = origInstrumentSync.apply(this.instrumenter, args), + baseline = this.instrumenter.lastFileCoverage(), + coverage = {}; + coverage[baseline.path] = baseline; + this.collector.add(coverage); + return ret; + }; + //monkey patch the instrumenter to call our version instead + instrumenter.instrumentSync = this.instrumentSync.bind(this); +} + +BaselineCollector.prototype = { + getCoverage: function () { + return this.collector.getFinalCoverage(); + } +}; + + +function processFiles(instrumenter, inputDir, outputDir, relativeNames, extensions) { + var processor = function (name, callback) { + var inputFile = path.resolve(inputDir, name), + outputFile = path.resolve(outputDir, name), + inputFileExtenstion = path.extname(inputFile), + isJavaScriptFile = extensions.indexOf(inputFileExtenstion) > -1, + oDir = path.dirname(outputFile), + readStream, writeStream; + + callback = once(callback); + mkdirp.sync(oDir); + + if (fs.statSync(inputFile).isDirectory()) { + return callback(null, name); + } + + if (isJavaScriptFile) { + fs.readFile(inputFile, 'utf8', function (err, data) { + if (err) { return callback(err, name); } + instrumenter.instrument(data, inputFile, function (iErr, instrumented) { + if (iErr) { return callback(iErr, name); } + fs.writeFile(outputFile, instrumented, 'utf8', function (err) { + return callback(err, name); + }); + }); + }); + } + else { + // non JavaScript file, copy it as is + readStream = fs.createReadStream(inputFile, {'bufferSize': READ_FILE_CHUNK_SIZE}); + writeStream = fs.createWriteStream(outputFile); + + readStream.on('error', callback); + writeStream.on('error', callback); + + readStream.pipe(writeStream); + readStream.on('end', function() { + callback(null, name); + }); + } + }, + q = async.queue(processor, 10), + errors = [], + count = 0, + startTime = new Date().getTime(); + + q.push(relativeNames, function (err, name) { + var inputFile, outputFile; + if (err) { + errors.push({ file: name, error: err.message || err.toString() }); + inputFile = path.resolve(inputDir, name); + outputFile = path.resolve(outputDir, name); + fs.writeFileSync(outputFile, fs.readFileSync(inputFile)); + } + if (verbose) { + console.log('Processed: ' + name); + } else { + if (count % 100 === 0) { process.stdout.write('.'); } + } + count += 1; + }); + + q.drain = function () { + var endTime = new Date().getTime(); + console.log('\nProcessed [' + count + '] files in ' + Math.floor((endTime - startTime) / 1000) + ' secs'); + if (errors.length > 0) { + console.log('The following ' + errors.length + ' file(s) had errors and were copied as-is'); + console.log(errors); + } + }; +} + + +function InstrumentCommand() { + Command.call(this); +} + +InstrumentCommand.TYPE = 'instrument'; +util.inherits(InstrumentCommand, Command); + +Command.mix(InstrumentCommand, { + synopsis: function synopsis() { + return "instruments a file or a directory tree and writes the instrumented code to the desired output location"; + }, + + usage: function () { + console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> <file-or-directory>\n\nOptions are:\n\n' + + [ + formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'), + formatOption('--output <file-or-dir>', 'The output file or directory. This is required when the input is a directory, ' + + 'defaults to standard output when input is a file'), + formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more glob patterns (e.g. "**/vendor/**" to ignore all files ' + + 'under a vendor directory). Also see the --default-excludes option'), + formatOption('--variable <global-coverage-variable-name>', 'change the variable name of the global coverage variable from the ' + + 'default value of `__coverage__` to something else'), + formatOption('--embed-source', 'embed source code into the coverage object, defaults to false'), + formatOption('--[no-]compact', 'produce [non]compact output, defaults to compact'), + formatOption('--[no-]preserve-comments', 'remove / preserve comments in the output, defaults to false'), + formatOption('--[no-]complete-copy', 'also copy non-javascript files to the ouput directory as is, defaults to false'), + formatOption('--save-baseline', 'produce a baseline coverage.json file out of all files instrumented'), + formatOption('--baseline-file <file>', 'filename of baseline file, defaults to coverage/coverage-baseline.json'), + formatOption('--es-modules', 'source code uses es import/export module syntax') + ].join('\n\n') + '\n'); + console.error('\n'); + }, + + run: function (args, callback) { + + var template = { + config: path, + output: path, + x: [Array, String], + variable: String, + compact: Boolean, + 'complete-copy': Boolean, + verbose: Boolean, + 'save-baseline': Boolean, + 'baseline-file': path, + 'embed-source': Boolean, + 'preserve-comments': Boolean, + 'es-modules': Boolean + }, + opts = nopt(template, { v : '--verbose' }, args, 0), + overrides = { + verbose: opts.verbose, + instrumentation: { + variable: opts.variable, + compact: opts.compact, + 'embed-source': opts['embed-source'], + 'preserve-comments': opts['preserve-comments'], + excludes: opts.x, + 'complete-copy': opts['complete-copy'], + 'save-baseline': opts['save-baseline'], + 'baseline-file': opts['baseline-file'], + 'es-modules': opts['es-modules'] + } + }, + config = configuration.loadFile(opts.config, overrides), + iOpts = config.instrumentation, + cmdArgs = opts.argv.remain, + file, + stats, + stream, + includes, + instrumenter, + needBaseline = iOpts.saveBaseline(), + baselineFile = path.resolve(iOpts.baselineFile()), + output = opts.output; + + verbose = config.verbose; + if (cmdArgs.length !== 1) { + return callback(inputError.create('Need exactly one filename/ dirname argument for the instrument command!')); + } + + if (iOpts.completeCopy()) { + includes = ['**/*']; + } + else { + includes = iOpts.extensions().map(function(ext) { + return '**/*' + ext; + }); + } + + instrumenter = new Instrumenter({ + coverageVariable: iOpts.variable(), + embedSource: iOpts.embedSource(), + noCompact: !iOpts.compact(), + preserveComments: iOpts.preserveComments(), + esModules: iOpts.esModules() + }); + + if (needBaseline) { + mkdirp.sync(path.dirname(baselineFile)); + instrumenter = new BaselineCollector(instrumenter); + process.on('exit', function () { + console.log('Saving baseline coverage at: ' + baselineFile); + fs.writeFileSync(baselineFile, JSON.stringify(instrumenter.getCoverage()), 'utf8'); + }); + } + + file = path.resolve(cmdArgs[0]); + stats = fs.statSync(file); + if (stats.isDirectory()) { + if (!output) { return callback(inputError.create('Need an output directory [-o <dir>] when input is a directory!')); } + if (output === file) { return callback(inputError.create('Cannot instrument into the same directory/ file as input!')); } + mkdirp.sync(output); + filesFor({ + root: file, + includes: includes, + excludes: opts.x || iOpts.excludes(false), // backwards-compat, *sigh* + relative: true + }, function (err, files) { + if (err) { return callback(err); } + processFiles(instrumenter, file, output, files, iOpts.extensions()); + }); + } else { + if (output) { + stream = fs.createWriteStream(output); + } else { + stream = process.stdout; + } + stream.write(instrumenter.instrumentSync(fs.readFileSync(file, 'utf8'), file)); + if (stream !== process.stdout) { + stream.end(); + } + } + } +}); + +module.exports = InstrumentCommand; + diff --git a/node_modules/istanbul/lib/command/report.js b/node_modules/istanbul/lib/command/report.js new file mode 100644 index 000000000..7abc52cfd --- /dev/null +++ b/node_modules/istanbul/lib/command/report.js @@ -0,0 +1,123 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var nopt = require('nopt'), + Report = require('../report'), + Reporter = require('../reporter'), + path = require('path'), + fs = require('fs'), + Collector = require('../collector'), + helpFormatter = require('../util/help-formatter'), + formatOption = helpFormatter.formatOption, + formatPara = helpFormatter.formatPara, + filesFor = require('../util/file-matcher').filesFor, + util = require('util'), + Command = require('./index'), + configuration = require('../config'); + +function ReportCommand() { + Command.call(this); +} + +ReportCommand.TYPE = 'report'; +util.inherits(ReportCommand, Command); + +function printDeprecationMessage(pat, fmt) { + console.error('**********************************************************************'); + console.error('DEPRECATION WARNING! You are probably using the old format of the report command'); + console.error('This will stop working soon, see `istanbul help report` for the new command format'); + console.error('Assuming you meant: istanbul report --include=' + pat + ' ' + fmt); + console.error('**********************************************************************'); +} + +Command.mix(ReportCommand, { + synopsis: function () { + return "writes reports for coverage JSON objects produced in a previous run"; + }, + + usage: function () { + console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> [ <format> ... ]\n\nOptions are:\n\n' + + [ + formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'), + formatOption('--root <input-directory>', 'The input root directory for finding coverage files'), + formatOption('--dir <report-directory>', 'The output directory where files will be written. This defaults to ./coverage/'), + formatOption('--include <glob>', 'The glob pattern to select one or more coverage files, defaults to **/coverage*.json'), + formatOption('--verbose, -v', 'verbose mode') + ].join('\n\n')); + + console.error('\n'); + console.error('<format> is one of '); + Report.getReportList().forEach(function (name) { + console.error(formatOption(name, Report.create(name).synopsis())); + }); + console.error(""); + console.error(formatPara([ + 'Default format is lcov unless otherwise specified in the config file.', + 'In addition you can tweak the file names for various reports using the config file.', + 'Type `istanbul help config` to see what can be tweaked.' + ].join(' '))); + console.error('\n'); + }, + + run: function (args, callback) { + + var template = { + config: path, + root: path, + dir: path, + include: String, + verbose: Boolean + }, + opts = nopt(template, { v : '--verbose' }, args, 0), + includePattern = opts.include || '**/coverage*.json', + root, + collector = new Collector(), + config = configuration.loadFile(opts.config, { + verbose: opts.verbose, + reporting: { + dir: opts.dir + } + }), + formats = opts.argv.remain, + reporter = new Reporter(config); + + // Start: backward compatible processing + if (formats.length === 2 && + Report.getReportList().indexOf(formats[1]) < 0) { + includePattern = formats[1]; + formats = [ formats[0] ]; + printDeprecationMessage(includePattern, formats[0]); + } + // End: backward compatible processing + + if (formats.length === 0) { + formats = config.reporting.reports(); + } + if (formats.length === 0) { + formats = [ 'lcov' ]; + } + reporter.addAll(formats); + + root = opts.root || process.cwd(); + filesFor({ + root: root, + includes: [ includePattern ] + }, function (err, files) { + if (err) { throw err; } + files.forEach(function (file) { + var coverageObject = JSON.parse(fs.readFileSync(file, 'utf8')); + collector.add(coverageObject); + }); + reporter.write(collector, false, function (err) { + console.log('Done'); + return callback(err); + }); + }); + } +}); + +module.exports = ReportCommand; + + diff --git a/node_modules/istanbul/lib/command/test.js b/node_modules/istanbul/lib/command/test.js new file mode 100644 index 000000000..59305074c --- /dev/null +++ b/node_modules/istanbul/lib/command/test.js @@ -0,0 +1,31 @@ +/* + Copyright (c) 2012, Yahoo! Inc. All rights reserved. + Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + +var runWithCover = require('./common/run-with-cover'), + util = require('util'), + Command = require('./index'); + +function TestCommand() { + Command.call(this); +} + +TestCommand.TYPE = 'test'; +util.inherits(TestCommand, Command); + +Command.mix(TestCommand, { + synopsis: function () { + return "cover a node command only when npm_config_coverage is set. Use in an `npm test` script for conditional coverage"; + }, + + usage: function () { + runWithCover.usage(this.toolName(), this.type()); + }, + + run: function (args, callback) { + runWithCover.run(args, this.type(), !!process.env.npm_config_coverage, callback); + } +}); + +module.exports = TestCommand; |