diff options
Diffstat (limited to 'node_modules/istanbul/lib/command/common')
-rw-r--r-- | node_modules/istanbul/lib/command/common/run-with-cover.js | 261 |
1 files changed, 261 insertions, 0 deletions
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 +}; |