diff --git a/bin/jest.js b/bin/jest.js index fdd837a96d49..89e6598f5467 100755 --- a/bin/jest.js +++ b/bin/jest.js @@ -9,7 +9,7 @@ 'use strict'; -const fs = require('fs'); +const fs = require('graceful-fs'); const optimist = require('optimist'); const path = require('path'); diff --git a/package.json b/package.json index 3ab8a2b36ac0..1d565cd65273 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "chalk": "^1.1.1", "cover": "^0.2.9", "diff": "^2.1.1", - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.1.3", "istanbul": "^0.4.2", "jsdom": "^7.2.0", "json-stable-stringify": "^1.0.0", "lodash.template": "^3.6.2", + "mkdirp": "^0.5.1", "node-haste": "2.0.0-alpha16", "optimist": "^0.6.1", "resolve": "^1.1.6", diff --git a/src/HasteModuleLoader/HasteModuleLoader.js b/src/HasteModuleLoader/HasteModuleLoader.js index e19d40a4ed17..966ce4e7a45a 100644 --- a/src/HasteModuleLoader/HasteModuleLoader.js +++ b/src/HasteModuleLoader/HasteModuleLoader.js @@ -25,6 +25,9 @@ const mockParentModule = { exports: {}, }; +const normalizedIDCache = Object.create(null); +const moduleNameCache = Object.create(null); + const isFile = file => { let stat; try { @@ -52,8 +55,8 @@ class Loader { this._shouldAutoMock = true; this._configShouldMockModuleNames = Object.create(null); this._extensions = config.moduleFileExtensions.map(ext => '.' + ext); - this._resolvedModules = moduleMap.resolvedModules; - this._resources = moduleMap.resources; + + this._modules = moduleMap.modules; this._mocks = moduleMap.mocks; if (config.collectCoverage) { @@ -366,14 +369,16 @@ class Loader { _resolveModuleName(currPath, moduleName) { // Check if the resolver knows about this module - if ( - this._resolvedModules[currPath] && - this._resolvedModules[currPath][moduleName] - ) { - return this._resolvedModules[currPath][moduleName]; + if (this._modules[moduleName]) { + return this._modules[moduleName]; } else { // Otherwise it is likely a node_module. - return this._resolveNodeModule(currPath, moduleName); + const key = currPath + ' : ' + moduleName; + if (moduleNameCache[key]) { + return moduleNameCache[key]; + } + moduleNameCache[key] = this._resolveNodeModule(currPath, moduleName); + return moduleNameCache[key]; } } @@ -388,6 +393,17 @@ class Loader { readFileSync: fs.readFileSync, }); } catch (e) { + const parts = moduleName.split('/'); + const nodeModuleName = parts.shift(); + const module = this._getModule(nodeModuleName); + if (module) { + try { + return require.resolve( + path.join.apply(path, [path.dirname(module)].concat(parts)) + ); + } catch (ignoredError) {} + } + // resolve.sync uses the basedir instead of currPath and therefore // doesn't throw an accurate error message. const relativePath = path.relative(basedir, currPath); @@ -398,7 +414,7 @@ class Loader { } _getModule(resourceName) { - return this._resources[resourceName]; + return this._modules[resourceName]; } _getMockModule(resourceName) { @@ -413,6 +429,11 @@ class Loader { } _getNormalizedModuleID(currPath, moduleName) { + const key = currPath + ' : ' + moduleName; + if (normalizedIDCache[key]) { + return normalizedIDCache[key]; + } + let moduleType; let mockAbsPath = null; let realAbsPath = null; @@ -459,7 +480,9 @@ class Loader { } const delimiter = path.delimiter; - return moduleType + delimiter + realAbsPath + delimiter + mockAbsPath; + const id = moduleType + delimiter + realAbsPath + delimiter + mockAbsPath; + normalizedIDCache[key] = id; + return id; } _shouldMock(currPath, moduleName) { diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-NODE_PATH-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-NODE_PATH-test.js index 2b9854ef0a56..2bf1d8d17d78 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-NODE_PATH-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-NODE_PATH-test.js @@ -31,7 +31,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js index 61b887e36094..3112004bab08 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-genMockFromModule-test.js @@ -31,7 +31,7 @@ describe('nodeHasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-getTestEnvData-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-getTestEnvData-test.js index 43f430cd7ab1..f6914681679c 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-getTestEnvData-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-getTestEnvData-test.js @@ -32,7 +32,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-jsdom-env-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-jsdom-env-test.js index c56cb7922b19..8879cd9cdd40 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-jsdom-env-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-jsdom-env-test.js @@ -30,7 +30,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireMock-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireMock-test.js index e9edabeb0710..5148aae65a24 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireMock-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireMock-test.js @@ -31,7 +31,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModule-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModule-test.js index a77c2d5b0a62..f51920ff233e 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModule-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModule-test.js @@ -31,7 +31,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModuleOrMock-test.js b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModuleOrMock-test.js index 431e1c2d3f29..b68855eea820 100644 --- a/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModuleOrMock-test.js +++ b/src/HasteModuleLoader/__tests__/HasteModuleLoader-requireModuleOrMock-test.js @@ -35,7 +35,7 @@ describe('HasteModuleLoader', function() { function buildLoader() { const environment = new JSDOMEnvironment(config); const resolver = new HasteResolver(config, {resetCache: false}); - return resolver.getDependencies(rootPath).then( + return resolver.getHasteMap().then( response => resolver.end().then(() => new HasteModuleLoader(config, environment, response) ) diff --git a/src/HasteModuleLoader/__tests__/HasteResolver-Cache-test.js b/src/HasteModuleLoader/__tests__/HasteResolver-Cache-test.js deleted file mode 100644 index bbbe997f6e78..000000000000 --- a/src/HasteModuleLoader/__tests__/HasteResolver-Cache-test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2014, Facebook, Inc. All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails oncall+jsinfra - */ -'use strict'; - -jest.autoMockOff(); - -const path = require('path'); -const utils = require('../../lib/utils'); - -describe('HasteResolver-Cache', () => { - let HasteResolver; - - const rootDir = path.resolve(__dirname, 'test_root'); - const rootPath = path.resolve(rootDir, 'root.js'); - const config = utils.normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'NodeHasteResolver-Cache', - rootDir, - }); - - const mockResolveDependencies = (instance) => { - const fn = instance._resolveDependencies; - instance._resolveDependencies = jest.genMockFn().mockImplementation(fn); - }; - - beforeEach(() => { - HasteResolver = require('../../resolvers/HasteResolver'); - }); - - describe('genMockFromModule', () => { - pit('', () => { - let resolver = new HasteResolver(config, {resetCache: true}); - mockResolveDependencies(resolver); - return resolver.getDependencies(rootPath).then( - response => { - expect(resolver._resolveDependencies).toBeCalled(); - return resolver.end(); - } - ).then(() => { - resolver = new HasteResolver(config, {resetCache: false}); - mockResolveDependencies(resolver); - return resolver.getDependencies(rootPath).then( - response => { - expect(resolver._resolveDependencies).not.toBeCalled(); - return resolver.end(); - } - ); - }).then(() => { - resolver = new HasteResolver(config, {resetCache: false}); - mockResolveDependencies(resolver); - resolver._validateCache = () => false; - return resolver.getDependencies(rootPath).then( - response => { - expect(resolver._resolveDependencies).toBeCalled(); - return resolver.end(); - } - ); - }); - }); - }); -}); diff --git a/src/Test.js b/src/Test.js index 3b8901c8372c..581bc21f9e7b 100644 --- a/src/Test.js +++ b/src/Test.js @@ -11,16 +11,16 @@ const Console = require('./Console'); class Test { - constructor(path, moduleMap, config) { + constructor(path, config, moduleMap) { this._path = path; - this._moduleMap = moduleMap; this._config = config; + this._moduleMap = moduleMap; } run() { const path = this._path; - const moduleMap = this._moduleMap; const config = this._config; + const moduleMap = this._moduleMap; const TestEnvironment = require(config.testEnvironment); const TestRunner = require(config.testRunner); const ModuleLoader = require(config.moduleLoader); diff --git a/src/TestRunner.js b/src/TestRunner.js index ca74525f9bc9..a9a9f6f8705f 100644 --- a/src/TestRunner.js +++ b/src/TestRunner.js @@ -12,21 +12,17 @@ const Test = require('./Test'); const fs = require('graceful-fs'); +const getCacheFilePath = require('node-haste/lib/Cache/lib/getCacheFilePath'); +const getCacheKey = require('./lib/getCacheKey'); +const mkdirp = require('mkdirp'); const os = require('os'); const path = require('path'); +const promisify = require('./lib/promisify'); const utils = require('./lib/utils'); const workerFarm = require('worker-farm'); -const promisify = require('./lib/promisify'); const TEST_WORKER_PATH = require.resolve('./TestWorker'); -const mergeModuleMap = (a, b) => { - Object.assign(a.mocks, b.mocks); - Object.assign(a.resolvedModules, b.resolvedModules); - Object.assign(a.resources, b.resources); - return a; -}; - const DEFAULT_OPTIONS = { /** @@ -73,6 +69,15 @@ class TestRunner { constructor(config, options) { this._opts = Object.assign({}, DEFAULT_OPTIONS, options); this._config = Object.freeze(config); + + try { + mkdirp.sync(this._config.cacheDirectory, '777'); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + } + const Resolver = require(config.moduleResolver); this._resolver = new Resolver(config, { resetCache: !config.cache, @@ -216,18 +221,17 @@ class TestRunner { _cacheTestResults(aggregatedResults) { const cacheFile = this._getTestPerformanceCachePath(); - let testPerformanceCache = this._testPerformanceCache; - if (!testPerformanceCache) { - testPerformanceCache = this._testPerformanceCache = {}; + let cache = this._testPerformanceCache; + if (!cache) { + cache = this._testPerformanceCache = {}; } aggregatedResults.testResults.forEach(test => { const perf = test && test.perfStats; if (perf && perf.end && perf.start) { - testPerformanceCache[test.testFilePath] = perf.end - perf.start; + cache[test.testFilePath] = perf.end - perf.start; } }); - const cache = JSON.stringify(testPerformanceCache); - return new Promise(resolve => fs.writeFile(cacheFile, cache, resolve)); + return promisify(fs.writeFile)(cacheFile, JSON.stringify(cache)); } runTests(testPaths, reporter) { @@ -328,105 +332,50 @@ class TestRunner { _createInBandTestRun(testPaths, onTestResult, onRunFailure) { return testPaths.reduce((promise, path) => promise - .then(() => this._resolveDependencies(path)) - .then(moduleMap => new Test(path, moduleMap, this._config).run()) + .then(() => this._resolver.getHasteMap()) + .then(moduleMap => new Test(path, this._config, moduleMap).run()) .then(result => onTestResult(path, result)) .catch(err => onRunFailure(path, err)), Promise.resolve() ); } - _createParallelTestRun(testPaths, onTestResult, onRunFailure) { - const config = this._config; - const farm = workerFarm({ - autoStart: true, - maxConcurrentCallsPerWorker: 1, - maxRetries: 2, // Allow for a couple of transient errors. - maxConcurrentWorkers: this._opts.maxWorkers, - }, TEST_WORKER_PATH); - const runTest = promisify(farm); - const deferreds = testPaths.map(path => { - let resolve; - const promise = new Promise(_resolve => resolve = _resolve); - return {resolve, promise}; - }); - let i = 0; - const nextResolution = () => { - if (i >= testPaths.length) { - return; - } - - const path = testPaths[i]; - const deferred = deferreds[i]; - const promise = this._resolveDependencies(path); - i++; - - const start = Date.now(); - console.log('resolving', path); - promise - .then(moduleMap => { - console.log((Date.now() - start) + 'ms', Object.keys(moduleMap.resources).length + 'res', 'running', path); - nextResolution(); - return runTest({path, moduleMap, config}); - }) - .then(testResult => onTestResult(path, testResult)) - .catch(err => { - onRunFailure(path, err); - if (err.type === 'ProcessTerminatedError') { - console.error( - 'A worker process has quit unexpectedly! ' + - 'Most likely this an initialization error.' - ); - process.exit(1); - } - }). - then(() => deferred.resolve()); - }; - - for (let i = 0; i < this._opts.maxWorkers; i++) { - nextResolution(); - } - return Promise.all(deferreds.map(deferred => deferred.promise)) - .then(() => workerFarm.end(farm)); + _persistModuleMap(moduleMap) { + const cacheFile = getCacheFilePath( + this._config.cacheDirectory, + getCacheKey('jest-module-map', this._config) + ); + return promisify(fs.writeFile)(cacheFile, JSON.stringify(moduleMap)); } - _resolveSetupFiles() { - if (this._setupFilePromise) { - return this._setupFilePromise; - } - + _createParallelTestRun(testPaths, onTestResult, onRunFailure) { const config = this._config; - const paths = []; - const moduleMap = { - mocks: Object.create(null), - resolvedModules: Object.create(null), - resources: Object.create(null), - }; - if (config.setupEnvScriptFile) { - paths.push(config.setupEnvScriptFile); - } - if (config.setupTestFrameworkScriptFile) { - paths.push(config.setupTestFrameworkScriptFile); - } - if (paths.length) { - return this._setupFilePromise = Promise.all( - paths.map(p => this._resolver.getDependencies(p)) - ).then(moduleMaps => { - moduleMaps.forEach(map => mergeModuleMap(moduleMap, map)); - return moduleMap; + return this._resolver.getHasteMap() + .then(moduleMap => this._persistModuleMap(moduleMap)) + .then(() => { + const farm = workerFarm({ + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxRetries: 2, // Allow for a couple of transient errors. + maxConcurrentWorkers: this._opts.maxWorkers, + }, TEST_WORKER_PATH); + const runTest = promisify(farm); + return Promise.all(testPaths.map( + path => runTest({path, config}) + .then(testResult => onTestResult(path, testResult)) + .catch(err => { + onRunFailure(path, err); + if (err.type === 'ProcessTerminatedError') { + console.error( + 'A worker process has quit unexpectedly! ' + + 'Most likely this an initialization error.' + ); + process.exit(1); + } + })) + ) + .then(() => workerFarm.end(farm)); }); - } else { - return this._setupFilePromise = Promise.resolve(moduleMap); - } - } - - _resolveDependencies(path) { - return Promise.all([ - this._resolveSetupFiles(), - this._resolver.getDependencies(path), - ]).then( - moduleMaps => mergeModuleMap(moduleMaps[1], moduleMaps[0]) - ); } } diff --git a/src/TestWorker.js b/src/TestWorker.js index 8f1ee5bb444b..01967ce90b4b 100644 --- a/src/TestWorker.js +++ b/src/TestWorker.js @@ -15,16 +15,29 @@ process.on('uncaughtException', err => { const Test = require('./Test'); +const fs = require('graceful-fs'); +const getCacheFilePath = require('node-haste/lib/Cache/lib/getCacheFilePath'); +const getCacheKey = require('./lib/getCacheKey'); + +let moduleMap; + module.exports = (data, callback) => { try { - new Test(data.path, data.moduleMap, data.config) + if (!moduleMap) { + const cacheFile = getCacheFilePath( + data.config.cacheDirectory, + getCacheKey('jest-module-map', data.config) + ); + moduleMap = JSON.parse(fs.readFileSync(cacheFile)); + } + + new Test(data.path, data.config, moduleMap) .run() .then( result => callback(null, result), - // TODO: move to error object passing (why limit to strings?). - err => callback(err.stack || err.message || err) + err => callback(err) ); } catch (err) { - callback(err.stack || err.message || err); + callback(err); } }; diff --git a/src/jest.js b/src/jest.js index f81185fbe09d..da234bb0234a 100644 --- a/src/jest.js +++ b/src/jest.js @@ -7,8 +7,11 @@ */ 'use strict'; +const realFs = require('fs'); +const fs = require('graceful-fs'); +fs.gracefulify(realFs); + const childProcess = require('child_process'); -const fs = require('fs'); const path = require('path'); const TestRunner = require('./TestRunner'); const formatTestResults = require('./lib/formatTestResults'); diff --git a/src/lib/getCacheKey.js b/src/lib/getCacheKey.js new file mode 100644 index 000000000000..475b7bd5c8b6 --- /dev/null +++ b/src/lib/getCacheKey.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2014, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const version = require('../../package').version; + +module.exports = function(name, config) { + return [ + name, + version, + config.name, + config.testPathDirs.join(';'), + config.cacheDirectory, + config.modulePathIgnorePatterns.toString(), + config.moduleFileExtensions.toString(), + config.testFileExtensions.toString(), + ].join('$'); +}; diff --git a/src/lib/transform.js b/src/lib/transform.js index 3c0fff7c2de6..9912cddb73b5 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -11,23 +11,11 @@ const crypto = require('crypto'); const fs = require('graceful-fs'); const path = require('path'); const stableStringify = require('json-stable-stringify'); +const mkdirp = require('mkdirp'); const cache = new Map(); const configToJsonMap = new Map(); -const createDirectory = path => { - if (!fs.existsSync(path)) { - try { - fs.mkdirSync(path); - } catch (e) { - if (e.code !== 'EEXIST') { - throw e; - } - } - fs.chmodSync(path, '777'); - } -}; - const removeFile = path => { try { fs.unlinkSync(path); @@ -122,10 +110,15 @@ module.exports = (filePath, config) => { cacheDir, path.basename(filePath, path.extname(filePath)) + '_' + cacheKey ); - - createDirectory(config.cacheDirectory); - createDirectory(baseCacheDir); - createDirectory(cacheDir); + + try { + mkdirp.sync(cacheDir, '777'); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + } + const cachedData = readCacheFile(filePath, cachePath); if (cachedData) { fileData = cachedData; diff --git a/src/lib/utils.js b/src/lib/utils.js index 6f970254253e..baaa77a6b560 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -289,6 +289,17 @@ function loadConfigFromPackageJson(filePath) { }); } +const KEEP_TRACE_LINES = 2; +function cleanStackTrace(stackTrace) { + // Remove jasmine jonx from the stack trace + let lines = 0; + const keepFirstLines = () => (lines++ < KEEP_TRACE_LINES); + return stackTrace.split('\n').filter(line => ( + keepFirstLines() || + !/jest(-cli)?\/(vendor|src|node_modules)\//.test(line) + )).join('\n'); +} + /** * Given a test result, return a human readable string representing the * failures. @@ -313,7 +324,7 @@ function formatFailureMessage(testResult, config) { const error = testResult.testExecError; return ( descBullet + localChalk.bold('Runtime Error') + '\n' + - error.message + '\n' + error.stack + error.message + (error.stack ? '\n' + cleanStackTrace(error.stack) : '') ); } @@ -382,4 +393,5 @@ exports.escapeStrForRegex = escapeStrForRegex; exports.loadConfigFromFile = loadConfigFromFile; exports.loadConfigFromPackageJson = loadConfigFromPackageJson; exports.normalizeConfig = normalizeConfig; +exports.cleanStackTrace = cleanStackTrace; exports.formatFailureMessage = formatFailureMessage; diff --git a/src/resolvers/HasteResolver.js b/src/resolvers/HasteResolver.js index b8febdc48ec4..547791380982 100644 --- a/src/resolvers/HasteResolver.js +++ b/src/resolvers/HasteResolver.js @@ -15,12 +15,11 @@ const DependencyGraph = require('node-haste'); const FileWatcher = require('node-haste/lib/FileWatcher'); const extractRequires = require('node-haste/lib/lib/extractRequires'); +const getCacheKey = require('../lib/getCacheKey'); +const path = require('path'); -const version = require('../../package').version; const REQUIRE_EXTENSIONS_PATTERN = /(\b(?:require\s*?\.\s*?(?:requireActual|requireMock)|jest\s*?\.\s*?genMockFromModule)\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; -const cacheFlag = Promise.resolve(1); - class HasteResolver { constructor(config, options) { @@ -31,18 +30,13 @@ class HasteResolver { ); this._defaultPlatform = config.haste.defaultPlatform; - this._resolvePromises = Object.create(null); + this._hasteMapPromise = null; + this._mocksPattern = new RegExp(config.mocksPattern); this._cache = new Cache({ resetCache: options.resetCache, cacheDirectory: config.cacheDirectory, - cacheKey: [ - 'jest', - version, - config.name, - config.testPathDirs.join(';'), - ignoreFilePattern.toString(), - ].concat(extensions).join('$'), + cacheKey: getCacheKey('jest-haste-map', config), }); this._fileWatcher = new FileWatcher( @@ -94,39 +88,17 @@ class HasteResolver { return this._depGraph.getModuleForPath(path); } - getDependencies(path) { - if (this._resolvePromises[path]) { - return this._resolvePromises[path]; + getHasteMap() { + if (this._hasteMapPromise) { + return this._hasteMapPromise; } - let isCached = true; - return this._resolvePromises[path] = this._depGraph.load() - .then(() => this._cache.get( - path, - 'jestModuleMap', - () => { - isCached = false; - return this._resolveDependencies(path); - } - ).then( - moduleMap => { - if ( - !isCached || ( - this._validateCache(moduleMap.mocks) && - this._validateCache(moduleMap.resources) - ) - ) { - return moduleMap; - } - // If the cache was not generated *right now* or one of the - // recursive dependencies has changed, invalidate the cache - // and re-resolve the path. - this._cache.invalidate(path, 'jestModuleMap'); - delete this._resolvePromises[path]; - return this.getDependencies(path); - } - ) - ); + return this._hasteMapPromise = this._depGraph.load().then(() => { + return { + modules: this._getAllModules(), + mocks: this._getAllMocks(), + }; + }); } getShallowDependencies(path) { @@ -143,45 +115,24 @@ class HasteResolver { .then(() => Promise.all([this._fileWatcher.end(), this._cache.end()])); } - _resolveDependencies(path) { - return this._depGraph.getDependencies(path, this._defaultPlatform) - .then(response => - response.finalize().then(() => { - const deps = { - mocks: response.mocks, - resolvedModules: Object.create(null), - resources: Object.create(null), - }; - for (const resource in response.mocks) { - const resourcePath = response.mocks[resource]; - this._cache.set(resourcePath, 'jestCacheFlag', cacheFlag); - } - return Promise.all( - response.dependencies.map(module => { - if (!deps.resolvedModules[module.path]) { - deps.resolvedModules[module.path] = Object.create(null); - } - response.getResolvedDependencyPairs(module).forEach((pair) => - deps.resolvedModules[module.path][pair[0]] = pair[1].path - ); - this._cache.set(module.path, 'jestCacheFlag', cacheFlag); - return module.getName().then( - name => deps.resources[name] = module.path - ); - }) - ).then(() => deps); - }) - ); - } - - _validateCache(resources) { - for (const resource in resources) { - const path = resources[resource]; - if (!this._cache.has(path, 'jestCacheFlag')) { - return false; + _getAllModules() { + const map = this._depGraph._hasteMap._map; + const modules = Object.create(null); + for (const name in map) { + const module = map[name][this._defaultPlatform] || map[name].generic; + if (module) { + modules[name] = module.path; } } - return true; + return modules; + } + + _getAllMocks() { + const mocks = Object.create(null); + this._depGraph.getFS() + .matchFilesByPattern(new RegExp(this._mocksPattern)) + .forEach(file => mocks[path.basename(file, path.extname(file))] = file); + return mocks; } _updateModuleMappings(data) { diff --git a/src/testRunners/jasmine/JasmineFormatter.js b/src/testRunners/jasmine/JasmineFormatter.js index 30735d95558c..5d9b8beb6953 100644 --- a/src/testRunners/jasmine/JasmineFormatter.js +++ b/src/testRunners/jasmine/JasmineFormatter.js @@ -10,9 +10,9 @@ const diff = require('diff'); const chalk = require('chalk'); +const utils = require('../../lib/utils'); const ERROR_TITLE_COLOR = chalk.bold.underline.red; -const KEEP_TRACE_LINES = 2; const DIFFABLE_MATCHERS = Object.assign(Object.create(null), { toBe: true, toNotBe: true, @@ -69,7 +69,7 @@ class JasmineFormatter { // jasmine doesn't give us access to the actual Error object, so we // have to regexp out the message from the stack string in order to // colorize the `message` value - return this.cleanStackTrace(stackTrace.replace( + return utils.cleanStackTrace(stackTrace.replace( /(^(.|\n)*?(?=\n\s*at\s))/, ERROR_TITLE_COLOR('$1') )); @@ -146,18 +146,8 @@ class JasmineFormatter { } } - cleanStackTrace(stackTrace) { - // Remove jasmine jonx from the stack trace - let lines = 0; - const keepFirstLines = () => (lines++ < KEEP_TRACE_LINES); - return stackTrace.split('\n').filter(line => ( - keepFirstLines() || - !/jest(-cli)?\/(vendor|src|node_modules)\//.test(line) - )).join('\n'); - } - formatStackTrace(stackTrace, originalMessage, formattedMessage) { - return this.cleanStackTrace( + return utils.cleanStackTrace( stackTrace .replace(originalMessage, formattedMessage) .replace(/^.*Error:\s*/, '')