Permalink
Browse files

No more contexts - instrument the files directly (a la jscoverage)

  • Loading branch information...
1 parent 10ffce2 commit 52116c367d089cf75b53e89ff52293e367fff5c0 @itay committed Feb 28, 2012
Showing with 126 additions and 179 deletions.
  1. +26 −113 bin/cover
  2. +18 −0 coverage_store.js
  3. +51 −66 index.js
  4. +31 −0 templates/instrumentation_header.js
View
139 bin/cover
@@ -130,13 +130,23 @@
// Get configuration
var config = {};
if (cmdline.config || ".coverrc") {
- config = readConfigFile(cmdline.config || ".coverrc");
+ if (_.isObject(cmdline.config)) {
+ config = cmdline.config;
+ }
+ else {
+ config = readConfigFile(cmdline.config || ".coverrc");
+ }
}
// Get ignore
var ignore = {};
if (cmdline.ignore || config.ignore || ".coverignore") {
- ignore = readIgnoreFile(cmdline.ignore || config.ignore || ".coverignore");
+ if (_.isObject(cmdline.ignore)) {
+ ignore = cmdline.ignore;
+ }
+ else {
+ ignore = readIgnoreFile(cmdline.ignore || config.ignore || ".coverignore");
+ }
}
return {
@@ -149,122 +159,25 @@
/* ====== Context and Execution Handling ====== */
- var createContext = function(file, options, ignore) {
- file = fs.realpathSync(file);
-
- // Create our context
- var context = {};
-
- // Shallow copy the 'process' variable
- // and set argv appropriately, on both the
- // local and "sandbox" version of process.
- // This is to make sure that when we clone
- // process (which we do from our local
- // copy), we clone the right argv.
- process.stdin.resume();
- process.stdin.pause();
- var newProcess = {};
- for(var k in process) {
- newProcess[k] = process[k];
- }
- newProcess.argv = process.argv = ["node", file].concat(options);
- newProcess.reallyExit = function() {
- console.error("GO");
- };
- process.env._ = "";
-
- // Copy over all the globals
- for(var k in global) {
- context[k] = global[k];
- }
-
- // Copy over the 'module' variable
- // and set the filename appropriately,
- // so that require works properly.
- var newModule = {};
- for(var k in module) {
- newModule[k] = module[k];
- }
- newModule.parent = module;
-
- // We have to set it on the current module,
- // because that is what require is going to work
- // with the current module, not the child
- // module
- newModule.filename = file;
- newModule.paths = [];
- var dir = file;
- do {
- dir = path.dirname(dir);
- newModule.paths.push(path.join(dir, "node_modules"));
- } while (dir !== path.dirname(dir));
-
- // Copy over the require module
- // and set up require.module
- // to point to the right thing
- var newRequire = function(path) {
- return Module._load(path, newModule);
- };
- newRequire.resolve = function(request) {
- return Module._resolveFilename(request, newModule)[1];
- }
- newRequire.main = newModule;
- newRequire.cache = Module._cache;
-
- // Set everything into the context
- context.require = newRequire;
- context.__filename = file;
- context.__dirname = path.dirname(file);
- context.process = newProcess;
- context.console = console;
- context.module = newModule;
- context.exports = {};
- context.global = context;
-
- // We add some unique things to get the coverage hook to work
- context.__u_ignore = ignore;
-
- return context;
- };
-
// Run a file with coverage enabled in a new context
- var runFile = function(file, options, ignore) {
- options = options || [];
- var optionString = "";
- for(var i = 0; i < options.length; i++) {
- optionString += (options[i] + " ");
- }
-
+ var runFile = function(file, options, ignore) {
var file = path.resolve(file);
- var context = createContext(file, options, ignore);
-
- var code = fs.readFileSync(file, "utf-8");
- // Replace the #!/ in the beginning of executables, because runInNewContext
- // really doesn't like it.
- code = code.substr(0, "#!/".length) === "#!/" ? code.replace("#!/", "//#!/") : code;
+ var coverage = cover.cover(null, ignore);
- var pathToCover = path.resolve(path.resolve(__dirname), "..", "index.js");
- delete require.cache[pathToCover];
-
- var template = _.template(
- "var cover = require('<%= cover %>'); var coverage = cover.cover(null, __u_ignore, global);\n" +
- "(function() {\n" +
- "process.nextTick(function() { try { <%=code%>\n } catch(ex) { /* DO NOTHING */ } });" +
- "})();\n" +
- "coverage;"
- );
- var codeToRun = template({
- cover: pathToCover,
- code: code
+ process.nextTick(function() {
+ try {
+ // Load up the new argv
+ options = options || [];
+ process.argv = ["node", file].concat(options)
+
+ // Load the file as the main module
+ Module.runMain(file, null, true)
+ }
+ catch(ex) {
+ console.log("AAA", ex.stack);
+ }
});
- process.on('uncaughtException', function() { /* DO NOTHING */ })
- try {
- var coverage = vm.runInNewContext(codeToRun, context, file);
- }
- catch(ex) {
- // Do nothing
- }
return coverage;
};
View
@@ -0,0 +1,18 @@
+// Copyright 2011 Itay Neeman
+//
+// Licensed under the MIT License
+
+(function() {
+ var coverageStore = {};
+
+ module.exports = {};
+ module.exports.register = function(filename) {
+ var store = coverageStore[filename] = coverageStore[filename] || {nodes: {}, blocks: {}};
+
+ return store;
+ }
+
+ module.exports.getStore = function(filename) {
+ return coverageStore[filename] || {};
+ }
+})();
View
117 index.js
@@ -198,9 +198,27 @@ CoverageData.prototype.coverage = function() {
return coverage;
};
+CoverageData.prototype.prepare = function() {
+ var store = require('./coverage_store').getStore(this.filename);
+
+ for(var index in store.nodes) {
+ if (store.nodes.hasOwnProperty(index)) {
+ this.nodes[index] = {node: this.instrumentor.nodes[index], count: store.nodes[index].count};
+ }
+ }
+
+ for(var index in store.blocks) {
+ if (store.blocks.hasOwnProperty(index)) {
+ this.visitedBlocks[index] = {count: store.blocks[index].count};
+ }
+ }
+};
+
// Get statistics for the entire file, including per-line code coverage
// and block-level coverage
CoverageData.prototype.stats = function() {
+ this.prepare();
+
var missing = this.missing();
var filedata = fs.readFileSync(this.filename, 'utf8').split('\n');
@@ -243,64 +261,37 @@ CoverageData.prototype.stats = function() {
};
};
-var globals = {};
-
-// Create the execution environment for the file
-var createEnvironment = function(module, filename) {
- // Create a new requires
- var req = function(path) {
- return Module._load(path, module);
- };
-
- // Add various pieces of information to it
- req.resolve = function(request) {
- return Module._resolveFilename(request, module)[1];
- };
-
- req.paths = Module._paths;
- req.main = process.mainModule;
- req.extensions = Module._extensions;
- req.registerExtension = function() {
- throw new Error('require.registerExtension() removed. Use ' +
- 'require.extensions instead.');
- }
- require.cache = Module._cache;
-
- // Copy over the globals
- var g = globals[module.parent.filename];
- var ctxt = {};
- for(var k in g) {
- ctxt[k] = g[k];
- }
-
- // And create our context
- ctxt.require = req;
- ctxt.exports = module.exports;
- ctxt.__filename = filename;
- ctxt.__dirname = path.dirname(filename);
- ctxt.process = process;
- ctxt.console = console;
- ctxt.module = module;
- ctxt.global = ctxt;
-
- globals[module.filename] = ctxt;
-
- return ctxt;
-};
-
// Require the CLI module of cover and return it,
// in case anyone wants to use it programmatically
var cli = function() {
return require('./bin/cover');
-}
+};
-var cover = function(fileRegex, ignore, passedInGlobals) {
- globals[module.parent.filename] = passedInGlobals;
+var addInstrumentationHeader = function(template, filename, instrumented, coverageStorePath) {
+ var template = _.template(template);
+ var header = template({
+ instrumented: instrumented,
+ coverageStorePath: coverageStorePath,
+ filename: filename
+ });
+ return header + instrumented.instrumentedSource;
+};
+
+var stripBOM = function(content) {
+ // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
+ // because the buffer-to-string conversion in `fs.readFileSync()`
+ // translates it to FEFF, the UTF-16 BOM.
+ if (content.charCodeAt(0) === 0xFEFF) {
+ content = content.slice(1);
+ }
+ return content;
+}
+
+var cover = function(fileRegex, ignore) {
var originalRequire = require.extensions['.js'];
var coverageData = {};
var match = null;
- var target = this;
ignore = ignore || {};
@@ -311,8 +302,13 @@ var cover = function(fileRegex, ignore, passedInGlobals) {
match = new RegExp(fileRegex ? (fileRegex.replace(/\//g, '\\/').replace(/\./g, '\\.')) : ".*", '');
}
+ var pathToCoverageStore = path.resolve(path.resolve(__dirname), "coverage_store.js");
+ var templatePath = path.resolve(path.resolve(__dirname), "templates", "instrumentation_header.js");
+ var template = fs.readFileSync(templatePath, 'utf-8');
+
require.extensions['.js'] = function(module, filename) {
if(!match.test(filename)) return originalRequire(module, filename);
+ if(filename === pathToCoverageStore) return originalRequire(module, filename);
// If the specific file is to be ignored
var full = path.resolve(filename);
@@ -328,25 +324,15 @@ var cover = function(fileRegex, ignore, passedInGlobals) {
}
} while(full !== path.dirname(full));
- // Create the context, read the file, instrument it, and setup
- // the coverage tracker
- var context = target.createEnvironment(module, filename);
- var data = fs.readFileSync(filename, 'utf8').toString();
+ var data = stripBOM(fs.readFileSync(filename, 'utf8'));
+ data = data.replace(/^\#\!.*/, '');
+
var instrumented = instrument(data);
var coverage = coverageData[filename] = new CoverageData(filename, instrumented);
-
- // Add the coverage logic to the instrumented source
- instrumented.on('node', coverage.visit.bind(coverage));
- instrumented.on('block', coverage.visitBlock.bind(coverage));
- instrumented.addToContext(context);
- // Instrument the code
- var wrapper = '(function(ctxt) { with(ctxt) { return '+Module.wrap(instrumented.instrumentedSource)+'; } })';
- var compiledWrapper = vm.runInThisContext(wrapper, filename, true)(context);
-
- // And execute it
- var args = [context.exports, context.require, module, filename, context.__dirname];
- return compiledWrapper.apply(module.exports, args);
+ var newCode = addInstrumentationHeader(template, filename, instrumented, pathToCoverageStore);
+
+ return module._compile(newCode, filename);
};
// Setup the data retrieval and release functions
@@ -363,7 +349,6 @@ var cover = function(fileRegex, ignore, passedInGlobals) {
module.exports = {
cover: cover,
- createEnvironment: createEnvironment,
cli: cli,
hook: function() {
var c = cli();
@@ -0,0 +1,31 @@
+
+// Instrumentation Header
+{
+ var <%= instrumented.names.statement %>, <%= instrumented.names.expression %>, <%= instrumented.names.block %>;
+ var store = require('<%= coverageStorePath %>');
+ var data = store.register('<%= filename %>');
+ var nodes = data.nodes;
+ var blocks = data.blocks;
+
+ <%= instrumented.names.statement %> = function(i) {
+ var node = nodes[i] = (nodes[i] || {index: i, count:0})
+ node.count++;
+ };
+
+ <%= instrumented.names.expression %> = function(i) {
+ var node = nodes[i] = (nodes[i] || {index: i, count:0})
+ node.count++;
+
+ return function(expr) {
+ return expr;
+ }
+ };
+
+ <%= instrumented.names.block %> = function(i) {
+ var block = blocks[i] = (blocks[i] || {index: i, count:0})
+ block.count++;
+ };
+};
+////////////////////////
+
+

0 comments on commit 52116c3

Please sign in to comment.