react / wstein / node_modules / jest-cli / node_modules / istanbul / lib / command / common / run-with-cover.js
80684 views/*1Copyright (c) 2012, Yahoo! Inc. All rights reserved.2Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.3*/4var Module = require('module'),5path = require('path'),6fs = require('fs'),7nopt = require('nopt'),8which = require('which'),9mkdirp = require('mkdirp'),10existsSync = fs.existsSync || path.existsSync,11inputError = require('../../util/input-error'),12matcherFor = require('../../util/file-matcher').matcherFor,13Instrumenter = require('../../instrumenter'),14Collector = require('../../collector'),15formatOption = require('../../util/help-formatter').formatOption,16hook = require('../../hook'),17Reporter = require('../../reporter'),18resolve = require('resolve'),19configuration = require('../../config');2021function usage(arg0, command) {2223console.error('\nUsage: ' + arg0 + ' ' + command + ' [<options>] <executable-js-file-or-command> [-- <arguments-to-jsfile>]\n\nOptions are:\n\n'24+ [25formatOption('--config <path-to-config>', 'the configuration file to use, defaults to .istanbul.yml'),26formatOption('--root <path> ', 'the root path to look for files to instrument, defaults to .'),27formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more fileset patterns e.g. "**/vendor/**"'),28formatOption('-i <include-pattern> [-i <include-pattern>]', 'one or more fileset patterns e.g. "**/*.js"'),29formatOption('--[no-]default-excludes', 'apply default excludes [ **/node_modules/**, **/test/**, **/tests/** ], defaults to true'),30formatOption('--hook-run-in-context', 'hook vm.runInThisContext in addition to require (supports RequireJS), defaults to false'),31formatOption('--post-require-hook <file> | <module>', 'JS module that exports a function for post-require processing'),32formatOption('--report <format> [--report <format>] ', 'report format, defaults to lcov (= lcov.info + HTML)'),33formatOption('--dir <report-dir>', 'report directory, defaults to ./coverage'),34formatOption('--print <type>', 'type of report to print to console, one of summary (default), detail, both or none'),35formatOption('--verbose, -v', 'verbose mode'),36formatOption('--[no-]preserve-comments', 'remove / preserve comments in the output, defaults to false'),37formatOption('--include-all-sources', 'instrument all unused sources after running tests, defaults to false'),38formatOption('--[no-]include-pid', 'include PID in output coverage filename')39].join('\n\n') + '\n');40console.error('\n');41}4243function run(args, commandName, enableHooks, callback) {4445var template = {46config: path,47root: path,48x: [ Array, String ],49report: [Array, String ],50dir: path,51verbose: Boolean,52yui: Boolean,53'default-excludes': Boolean,54print: String,55'self-test': Boolean,56'hook-run-in-context': Boolean,57'post-require-hook': String,58'preserve-comments': Boolean,59'include-all-sources': Boolean,60'preload-sources': Boolean,61i: [ Array, String ],62'include-pid': Boolean63},64opts = nopt(template, { v : '--verbose' }, args, 0),65overrides = {66verbose: opts.verbose,67instrumentation: {68root: opts.root,69'default-excludes': opts['default-excludes'],70excludes: opts.x,71'include-all-sources': opts['include-all-sources'],72'preload-sources': opts['preload-sources'],73'include-pid': opts['include-pid']74},75reporting: {76reports: opts.report,77print: opts.print,78dir: opts.dir79},80hooks: {81'hook-run-in-context': opts['hook-run-in-context'],82'post-require-hook': opts['post-require-hook'],83'handle-sigint': opts['handle-sigint']84}85},86config = configuration.loadFile(opts.config, overrides),87verbose = config.verbose,88cmdAndArgs = opts.argv.remain,89preserveComments = opts['preserve-comments'],90includePid = opts['include-pid'],91cmd,92cmdArgs,93reportingDir,94reporter = new Reporter(config),95runFn,96excludes;9798if (cmdAndArgs.length === 0) {99return callback(inputError.create('Need a filename argument for the ' + commandName + ' command!'));100}101102cmd = cmdAndArgs.shift();103cmdArgs = cmdAndArgs;104105if (!existsSync(cmd)) {106try {107cmd = which.sync(cmd);108} catch (ex) {109return callback(inputError.create('Unable to resolve file [' + cmd + ']'));110}111} else {112cmd = path.resolve(cmd);113}114115runFn = function () {116process.argv = ["node", cmd].concat(cmdArgs);117if (verbose) {118console.log('Running: ' + process.argv.join(' '));119}120process.env.running_under_istanbul=1;121Module.runMain(cmd, null, true);122};123124excludes = config.instrumentation.excludes(true);125126if (enableHooks) {127reportingDir = path.resolve(config.reporting.dir());128mkdirp.sync(reportingDir); //ensure we fail early if we cannot do this129reporter.addAll(config.reporting.reports());130if (config.reporting.print() !== 'none') {131switch (config.reporting.print()) {132case 'detail':133reporter.add('text');134break;135case 'both':136reporter.add('text');137reporter.add('text-summary');138break;139default:140reporter.add('text-summary');141break;142}143}144145excludes.push(path.relative(process.cwd(), path.join(reportingDir, '**', '*')));146matcherFor({147root: config.instrumentation.root() || process.cwd(),148includes: opts.i || [ '**/*.js' ],149excludes: excludes150},151function (err, matchFn) {152if (err) { return callback(err); }153154var coverageVar = '$$cov_' + new Date().getTime() + '$$',155instrumenter = new Instrumenter({ coverageVariable: coverageVar , preserveComments: preserveComments}),156transformer = instrumenter.instrumentSync.bind(instrumenter),157hookOpts = { verbose: verbose },158postRequireHook = config.hooks.postRequireHook(),159postLoadHookFile;160161if (postRequireHook) {162postLoadHookFile = path.resolve(postRequireHook);163} else if (opts.yui) { //EXPERIMENTAL code: do not rely on this in anyway until the docs say it is allowed164postLoadHookFile = path.resolve(__dirname, '../../util/yui-load-hook');165}166167if (postRequireHook) {168if (!existsSync(postLoadHookFile)) { //assume it is a module name and resolve it169try {170postLoadHookFile = resolve.sync(postRequireHook, { basedir: process.cwd() });171} catch (ex) {172if (verbose) { console.error('Unable to resolve [' + postRequireHook + '] as a node module'); }173callback(ex);174return;175}176}177}178if (postLoadHookFile) {179if (verbose) { console.error('Use post-load-hook: ' + postLoadHookFile); }180hookOpts.postLoadHook = require(postLoadHookFile)(matchFn, transformer, verbose);181}182183if (opts['self-test']) {184hook.unloadRequireCache(matchFn);185}186// runInThisContext is used by RequireJS [issue #23]187if (config.hooks.hookRunInContext()) {188hook.hookRunInThisContext(matchFn, transformer, hookOpts);189}190hook.hookRequire(matchFn, transformer, hookOpts);191192//initialize the global variable to stop mocha from complaining about leaks193global[coverageVar] = {};194195// enable passing --handle-sigint to write reports on SIGINT.196// This allows a user to manually kill a process while197// still getting the istanbul report.198if (config.hooks.handleSigint()) {199process.once('SIGINT', process.exit);200}201202process.once('exit', function () {203var pidExt = includePid ? ('-' + process.pid) : '',204file = path.resolve(reportingDir, 'coverage' + pidExt + '.json'),205collector,206cov;207if (typeof global[coverageVar] === 'undefined' || Object.keys(global[coverageVar]).length === 0) {208console.error('No coverage information was collected, exit without writing coverage information');209return;210} else {211cov = global[coverageVar];212}213//important: there is no event loop at this point214//everything that happens in this exit handler MUST be synchronous215if (config.instrumentation.includeAllSources()) {216// Files that are not touched by code ran by the test runner is manually instrumented, to217// illustrate the missing coverage.218matchFn.files.forEach(function (file) {219if (!cov[file]) {220transformer(fs.readFileSync(file, 'utf-8'), file);221222// When instrumenting the code, istanbul will give each FunctionDeclaration a value of 1 in coverState.s,223// presumably to compensate for function hoisting. We need to reset this, as the function was not hoisted,224// as it was never loaded.225Object.keys(instrumenter.coverState.s).forEach(function (key) {226instrumenter.coverState.s[key] = 0;227});228229cov[file] = instrumenter.coverState;230}231});232}233mkdirp.sync(reportingDir); //yes, do this again since some test runners could clean the dir initially created234if (config.reporting.print() !== 'none') {235console.error('=============================================================================');236console.error('Writing coverage object [' + file + ']');237}238fs.writeFileSync(file, JSON.stringify(cov), 'utf8');239collector = new Collector();240collector.add(cov);241if (config.reporting.print() !== 'none') {242console.error('Writing coverage reports at [' + reportingDir + ']');243console.error('=============================================================================');244}245reporter.write(collector, true, callback);246});247runFn();248});249} else {250runFn();251}252}253254module.exports = {255run: run,256usage: usage257};258259260