Skip to content

Commit

Permalink
Simplify module execution.
Browse files Browse the repository at this point in the history
Summary:
Pulled out from #599.
Closes #636

Reviewed By: vjeux

Differential Revision: D2731701

fb-gh-sync-id: 23fbcc618dc9e0cfb79fb96a8d02302b3276466a
  • Loading branch information
cpojer authored and facebook-github-bot-6 committed Dec 7, 2015
1 parent bea5507 commit 878014e
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 79 deletions.
74 changes: 39 additions & 35 deletions src/HasteModuleLoader/HasteModuleLoader.js
Expand Up @@ -18,9 +18,7 @@ const os = require('os');
const path = require('path');
const resolve = require('resolve');
const transform = require('../lib/transform');
const utils = require('../lib/utils');

const COVERAGE_STORAGE_VAR_NAME = '____JEST_COVERAGE_DATA____';
const NODE_PATH =
(process.env.NODE_PATH ? process.env.NODE_PATH.split(path.delimiter) : null);
const IS_PATH_BASED_MODULE_NAME = /^(?:\.\.?\/|\/)/;
Expand Down Expand Up @@ -168,52 +166,58 @@ class Loader {
* objects.
*/
_execModule(moduleObj) {
const modulePath = moduleObj.__filename;
let moduleContent = transform(modulePath, this._config);
// If the environment was disposed, prevent this module from
// being executed.
if (!this._environment.global) {
return;
}

const filename = moduleObj.__filename;
let moduleContent = transform(filename, this._config);
let collectorStore;

// Every module, if loaded for jest, should have a parent
// so they don't think they are run standalone
// Every module receives a mock parent so they don't assume they are run
// standalone.
moduleObj.parent = mockParentModule;
moduleObj.require = this.constructBoundRequire(modulePath);

const moduleLocalBindings = {
module: moduleObj,
exports: moduleObj.exports,
require: moduleObj.require,
__dirname: path.dirname(modulePath),
__filename: modulePath,
global: this._environment.global,
jest: this._createRuntimeFor(modulePath),
};
moduleObj.require = this.constructBoundRequire(filename);

const onlyCollectFrom = this._config.collectCoverageOnlyFrom;
const shouldCollectCoverage =
this._config.collectCoverage === true && !onlyCollectFrom
|| (onlyCollectFrom && onlyCollectFrom[modulePath] === true);
(this._config.collectCoverage === true && !onlyCollectFrom) ||
(onlyCollectFrom && onlyCollectFrom[filename] === true);

if (shouldCollectCoverage) {
if (!hasOwnProperty.call(this._coverageCollectors, modulePath)) {
this._coverageCollectors[modulePath] =
new this._CoverageCollector(moduleContent, modulePath);
if (!hasOwnProperty.call(this._coverageCollectors, filename)) {
this._coverageCollectors[filename] =
new this._CoverageCollector(moduleContent, filename);
}
const collector = this._coverageCollectors[modulePath];
moduleLocalBindings[COVERAGE_STORAGE_VAR_NAME] =
collector.getCoverageDataStore();
const collector = this._coverageCollectors[filename];
collectorStore = collector.getCoverageDataStore();
moduleContent =
collector.getInstrumentedSource(COVERAGE_STORAGE_VAR_NAME);
collector.getInstrumentedSource('____JEST_COVERAGE_DATA____');
}

const lastExecutingModulePath = this._currentlyExecutingModulePath;
this._currentlyExecutingModulePath = modulePath;

this._currentlyExecutingModulePath = filename;
const origCurrExecutingManualMock = this._isCurrentlyExecutingManualMock;
this._isCurrentlyExecutingManualMock = modulePath;

utils.runContentWithLocalBindings(
this._environment,
moduleContent,
modulePath,
moduleLocalBindings
this._isCurrentlyExecutingManualMock = filename;

// Use this name for the module wrapper for consistency with node.
const evalResultVariable = 'Object.<anonymous>';
const wrapper = 'this["' + evalResultVariable + '"] = function(module, exports, require, __dirname, __filename, global, jest, ____JEST_COVERAGE_DATA____) {' + moduleContent + '\n};';
this._environment.runSourceText(wrapper, filename);
const wrapperFunc = this._environment.global[evalResultVariable];
delete this._environment.global[evalResultVariable];
wrapperFunc.call(
moduleObj.exports, // module context
moduleObj,
moduleObj.exports,
moduleObj.require,
path.dirname(filename),
filename,
this._environment.global,
this._createRuntimeFor(filename),
collectorStore
);

this._isCurrentlyExecutingManualMock = origCurrExecutingManualMock;
Expand Down
8 changes: 4 additions & 4 deletions src/environments/__mocks__/JSDOMEnvironment.js
Expand Up @@ -11,7 +11,7 @@ JSDOMEnvironment.mockImplementation(function(config) {
this.global = {
console: {},
mockClearTimers: jest.genMockFn(),
JSON: JSON,
JSON,
};

const globalValues = utils.deepCopy(config.globals);
Expand All @@ -21,9 +21,9 @@ JSDOMEnvironment.mockImplementation(function(config) {
});

JSDOMEnvironment.prototype.runSourceText.mockImplementation(
function(codeStr, fileName) {
vm.runInNewContext(codeStr, this.global, {
filename: fileName,
function(sourceText, filename) {
vm.runInNewContext(sourceText, this.global, {
filename,
displayErrors: false,
});
}
Expand Down
40 changes: 0 additions & 40 deletions src/lib/utils.js
Expand Up @@ -45,11 +45,6 @@ const DEFAULT_CONFIG_VALUES = {
useStderr: false,
};

// This shows up in the stack trace when a test file throws an unhandled error
// when evaluated. Node's require prints Object.<anonymous> when initializing
// modules, so do the same here solely for visual consistency.
const EVAL_RESULT_VARIABLE = 'Object.<anonymous>';

function _replaceRootDirTags(rootDir, config) {
switch (typeof config) {
case 'object':
Expand Down Expand Up @@ -367,40 +362,6 @@ function loadConfigFromPackageJson(filePath) {
});
}

function runContentWithLocalBindings(environment, scriptContent, scriptPath,
bindings) {
const boundIdents = Object.keys(bindings);
try {
const wrapperScript = 'this["' + EVAL_RESULT_VARIABLE + '"] = ' +
'function (' + boundIdents.join(',') + ') {' +
scriptContent +
'\n};';
environment.runSourceText(
wrapperScript,
scriptPath
);
} catch (e) {
e.message = scriptPath + ': ' + e.message;
throw e;
}

try {
const wrapperFunc = environment.global[EVAL_RESULT_VARIABLE];
delete environment.global[EVAL_RESULT_VARIABLE];

const bindingValues = boundIdents.map(function(ident) {
return bindings[ident];
});

// Node modules are executed with the `exports` as context.
// If not a node module then this should be undefined.
wrapperFunc.apply(bindings.exports, bindingValues);
} catch (e) {
e.message = scriptPath + ': ' + e.message;
throw e;
}
}

/**
* Given a test result, return a human readable string representing the
* failures.
Expand Down Expand Up @@ -503,5 +464,4 @@ exports.getLinePercentCoverageFromCoverageInfo =
exports.loadConfigFromFile = loadConfigFromFile;
exports.loadConfigFromPackageJson = loadConfigFromPackageJson;
exports.normalizeConfig = normalizeConfig;
exports.runContentWithLocalBindings = runContentWithLocalBindings;
exports.formatFailureMessage = formatFailureMessage;

0 comments on commit 878014e

Please sign in to comment.