aboutsummaryrefslogtreecommitdiff
path: root/node_modules/istanbul/lib/command/instrument.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/istanbul/lib/command/instrument.js')
-rw-r--r--node_modules/istanbul/lib/command/instrument.js265
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;
+