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