diff --git a/src/HasteModuleLoader/HasteModuleLoader.js b/src/HasteModuleLoader/HasteModuleLoader.js index 6d03f8d18f8c..dc52612942e4 100644 --- a/src/HasteModuleLoader/HasteModuleLoader.js +++ b/src/HasteModuleLoader/HasteModuleLoader.js @@ -311,61 +311,6 @@ class Loader { return collector ? collector.extractRuntimeCoverageInfo() : null; } - getDependentsFromPath(modulePath) { - const _getRealPathFromNormalizedModuleID = moduleID => { - return moduleID.split(path.delimiter)[1]; - }; - const _getDependencyPathsFromResource = resource => { - const dependencyPaths = []; - for (let i = 0; i < resource.requiredModules.length; i++) { - let requiredModule = resource.requiredModules[i]; - - // *facepalm* node-haste is pretty clowny - if (resource.getModuleIDByOrigin) { - requiredModule = - resource.getModuleIDByOrigin(requiredModule) || requiredModule; - } - - let moduleID; - try { - moduleID = this._getNormalizedModuleID(resource.path, requiredModule); - } catch (e) { - continue; - } - - dependencyPaths.push(_getRealPathFromNormalizedModuleID(moduleID)); - } - return dependencyPaths; - }; - - // TODO - if (this._reverseDependencyMap == null) { - const reverseDepMap = this._reverseDependencyMap = Object.create(null); - const allResources = []; - Object.keys(allResources).forEach(resourceID => { - const resource = allResources[resourceID]; - if ( - resource.type === 'ProjectConfiguration' || - resource.type === 'Resource' - ) { - return; - } - - const dependencyPaths = _getDependencyPathsFromResource(resource); - for (let i = 0; i < dependencyPaths.length; i++) { - const requiredModulePath = dependencyPaths[i]; - if (!reverseDepMap[requiredModulePath]) { - reverseDepMap[requiredModulePath] = Object.create(null); - } - reverseDepMap[requiredModulePath][resource.path] = true; - } - }); - } - - const reverseDeps = this._reverseDependencyMap[modulePath]; - return reverseDeps ? Object.keys(reverseDeps) : []; - } - _execModule(moduleObj) { // If the environment was disposed, prevent this module from // being executed. diff --git a/src/TestRunner.js b/src/TestRunner.js index bc068981d920..93690f22a7af 100644 --- a/src/TestRunner.js +++ b/src/TestRunner.js @@ -129,48 +129,37 @@ class TestRunner { return this._configDeps; } - promiseTestPathsRelatedTo(paths) { + promiseTestPathsRelatedTo(changedPaths) { + // TODO switch this to use HasteResolver instead of a ModuleLoader instance. return this._constructModuleLoader() - .then(moduleLoader => { - const relatedPaths = []; - const discoveredModules = {}; - - // If a path to a test file is given, make sure we consider that test as - // related to itself. Non-tests will be filtered at the end. - paths.forEach(path => { - discoveredModules[path] = true; - if (this._isTestFilePath(path) && fs.existsSync(path)) { - relatedPaths.push(path); - } - }); - - const modulesToSearch = [].concat(paths); - while (modulesToSearch.length > 0) { - const modulePath = modulesToSearch.shift(); - const depPaths = moduleLoader.getDependentsFromPath(modulePath); - - depPaths.forEach(depPath => { - if (!discoveredModules.hasOwnProperty(depPath)) { - discoveredModules[depPath] = true; - modulesToSearch.push(depPath); - if (this._isTestFilePath(depPath) && fs.existsSync(depPath)) { - relatedPaths.push(depPath); + .then(moduleLoader => moduleLoader.getAllTestPaths().then(paths => { + const allTests = {}; + return Promise.all( + paths.map(path => moduleLoader._resolver.getDependencies(path) + .then(deps => allTests[path] = deps) + ) + ).then(() => { + const relatedPaths = new Set(); + for (const path in allTests) { + if (this._isTestFilePath(path)) { + for (const resourcePath in allTests[path].resources) { + if (changedPaths.has(resourcePath)) { + relatedPaths.add(path); + } } } - }); - } - return relatedPaths; - }); + } + return Array.from(relatedPaths); + }); + })); } promiseTestPathsMatching(pathPattern) { + // TODO switch this to use HasteResolver instead of a ModuleLoader instance. return this._constructModuleLoader() .then(moduleLoader => moduleLoader.getAllTestPaths()) .then(testPaths => testPaths.filter( - path => ( - this._isTestFilePath(path) && - pathPattern.test(path) - ) + path => this._isTestFilePath(path) && pathPattern.test(path) )); } diff --git a/src/__tests__/TestRunner-test.js b/src/__tests__/TestRunner-test.js index eea9c6ad9ec2..cdf99a63e772 100644 --- a/src/__tests__/TestRunner-test.js +++ b/src/__tests__/TestRunner-test.js @@ -11,20 +11,19 @@ jest.autoMockOff().mock('fs'); -var name = 'TestRunner'; -describe('TestRunner', function() { - var TestRunner; +const name = 'TestRunner'; +describe('TestRunner', () => { + let TestRunner; - beforeEach(function() { + beforeEach(() => { TestRunner = require('../TestRunner'); }); - describe('_isTestFilePath', function() { - var runner; - var utils; + describe('_isTestFilePath', () => { + let runner; - beforeEach(function() { - utils = require('../lib/utils'); + beforeEach(() => { + const utils = require('../lib/utils'); runner = new TestRunner(utils.normalizeConfig({ cacheDirectory: global.CACHE_DIRECTORY, name, @@ -33,31 +32,37 @@ describe('TestRunner', function() { })); }); - it('supports ../ paths and unix separators', function() { - var path = '/path/to/__tests__/foo/bar/baz/../../../test.js'; - var isTestFile = runner._isTestFilePath(path); + it('supports ../ paths and unix separators', () => { + const path = '/path/to/__tests__/foo/bar/baz/../../../test.js'; + const isTestFile = runner._isTestFilePath(path); return expect(isTestFile).toEqual(true); }); - it('supports unix separators', function() { - var path = '/path/to/__tests__/test.js'; - var isTestFile = runner._isTestFilePath(path); + it('supports unix separators', () => { + const path = '/path/to/__tests__/test.js'; + const isTestFile = runner._isTestFilePath(path); return expect(isTestFile).toEqual(true); }); }); - describe('promiseTestPathsRelatedTo', function() { - var fakeDepsFromPath; - var fs; - var runner; - var utils; - - beforeEach(function() { - fs = require('graceful-fs'); - utils = require('../lib/utils'); + describe('promiseTestPathsRelatedTo', () => { + const allTests = [ + '__tests__/a.js', + '__tests__/b.js', + ]; + const dependencyGraph = { + '__tests__/a.js': {resources: {}}, + '__tests__/b.js': {resources: { + 'b.js': {}, + }}, + }; + let runner; + + beforeEach(() => { + const utils = require('../lib/utils'); runner = new TestRunner(utils.normalizeConfig({ cacheDirectory: global.CACHE_DIRECTORY, name, @@ -65,117 +70,34 @@ describe('TestRunner', function() { testPathDirs: [], })); - fakeDepsFromPath = {}; - runner._constructModuleLoader = function() { + runner._isTestFilePath = () => true; + runner._constructModuleLoader = () => { return Promise.resolve({ - getDependentsFromPath: function(modulePath) { - return fakeDepsFromPath[modulePath] || []; + getAllTestPaths: + modulePath => Promise.resolve(allTests), + _resolver: { + getDependencies: path => { + return Promise.resolve(dependencyGraph[path]); + }, }, }); }; }); - pit('finds no tests when no tests depend on the path', function() { - var path = '/path/to/module/not/covered/by/any/tests.js'; - fakeDepsFromPath[path] = []; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function() { return true; }; + pit('finds no tests when no tests depend on the path', () => { + const path = 'a.js'; - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { + return runner.promiseTestPathsRelatedTo(new Set([path])) + .then(relatedTests => { expect(relatedTests).toEqual([]); }); }); - pit('finds tests that depend directly on the path', function() { - var path = '/path/to/module/covered/by/one/test.js'; - var dependentTestPath = '/path/to/test/__tests__/asdf-test.js'; - fakeDepsFromPath[path] = [dependentTestPath]; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function() { return true; }; - - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { - expect(relatedTests).toEqual([dependentTestPath]); - }); - }); - - pit('finds tests that depend indirectly on the path', function() { - var path = '/path/to/module/covered/by/module/covered/by/test.js'; - var dependentModulePath = '/path/to/dependent/module.js'; - var dependentTestPath = '/path/to/test/__tests__/asdf-test.js'; - fakeDepsFromPath[path] = [dependentModulePath]; - fakeDepsFromPath[dependentModulePath] = [dependentTestPath]; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function() { return true; }; - - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { - expect(relatedTests).toEqual([dependentTestPath]); - }); - }); - - pit('finds multiple tests that depend indirectly on the path', function() { - var path = '/path/to/module/covered/by/modules/covered/by/test.js'; - var dependentModulePath1 = '/path/to/dependent/module1.js'; - var dependentModulePath2 = '/path/to/dependent/module2.js'; - var dependentTestPath1 = '/path/to/test1/__tests__/asdf1-test.js'; - var dependentTestPath2 = '/path/to/test2/__tests__/asdf2-test.js'; - fakeDepsFromPath[path] = [dependentModulePath1, dependentModulePath2]; - fakeDepsFromPath[dependentModulePath1] = [dependentTestPath1]; - fakeDepsFromPath[dependentModulePath2] = [dependentTestPath2]; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function() { return true; }; - - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { - expect(relatedTests).toEqual([ - dependentTestPath1, - dependentTestPath2, - ]); - }); - }); - - pit('flattens circular dependencies', function() { - var path = '/path/to/module/covered/by/modules/covered/by/test.js'; - var directDependentModulePath = '/path/to/direct/dependent/module.js'; - var indirectDependentModulePath = '/path/to/indirect/dependent/module.js'; - var dependentTestPath = '/path/to/test/__tests__/asdf-test.js'; - fakeDepsFromPath[path] = [directDependentModulePath]; - fakeDepsFromPath[directDependentModulePath] = - [indirectDependentModulePath]; - fakeDepsFromPath[indirectDependentModulePath] = [ - directDependentModulePath, - dependentTestPath, - ]; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function() { return true; }; - - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { - expect(relatedTests).toEqual([dependentTestPath]); - }); - }); - - pit('filters test paths that don\'t exist on the filesystem', function() { - var path = '/path/to/module/covered/by/one/test.js'; - var existingTestPath = '/path/to/test/__tests__/exists-test.js'; - var nonExistantTestPath = '/path/to/test/__tests__/doesnt-exist-test.js'; - fakeDepsFromPath[path] = [existingTestPath, nonExistantTestPath]; - - // Mock out existsSync to return true, since our test path isn't real - fs.existsSync = function(path) { - return path !== nonExistantTestPath; - }; - - return runner.promiseTestPathsRelatedTo([path]) - .then(function(relatedTests) { - expect(relatedTests).toEqual([existingTestPath]); + pit('finds tests that depend directly on the path', () => { + const path = 'b.js'; + return runner.promiseTestPathsRelatedTo(new Set([path])) + .then(relatedTests => { + expect(relatedTests).toEqual(['__tests__/b.js']); }); }); }); diff --git a/src/jest.js b/src/jest.js index ec5c07d8608a..8a575adf1f7d 100644 --- a/src/jest.js +++ b/src/jest.js @@ -23,10 +23,10 @@ function getVersion() { return jestVersion; } -function findChangedFiles(dirPath) { +function findChangedFiles(cwd) { return new Promise((resolve, reject) => { const args = ['diff', '--name-only', '--diff-filter=ACMR']; - const child = childProcess.spawn('git', args, {cwd: dirPath}); + const child = childProcess.spawn('git', args, {cwd}); let stdout = ''; let stderr = ''; @@ -39,7 +39,7 @@ function findChangedFiles(dirPath) { resolve([]); } else { resolve(stdout.split('\n').map( - changedPath => path.resolve(dirPath, changedPath) + changedPath => path.resolve(cwd, changedPath) )); } } else { @@ -49,14 +49,15 @@ function findChangedFiles(dirPath) { }); } -function verifyIsGitRepository(dirPath) { - return new Promise(resolve => - childProcess.spawn('git', ['rev-parse', '--git-dir'], {cwd: dirPath}) - .on('close', code => { - const isGitRepo = code === 0; - resolve(isGitRepo); - }) - ); +function isGitRepository(cwd) { + return new Promise(resolve => { + let stdout = ''; + const child = childProcess.spawn('git', ['rev-parse', '--git-dir'], {cwd}); + child.stdout.on('data', data => stdout += data); + child.on('close', + code => resolve(code === 0 ? path.dirname(stdout.trim()) : null) + ); + }); } function testRunnerOptions(argv) { @@ -146,29 +147,20 @@ function readRawConfig(argv, packageRoot) { } function findOnlyChangedTestPaths(testRunner, config) { - const testPathDirsAreGit = config.testPathDirs.map(verifyIsGitRepository); - return Promise.all(testPathDirsAreGit) - .then(results => { - if (!results.every(result => !!result)) { - /* eslint-disable no-throw-literal */ - throw ( + return Promise.all(config.testPathDirs.map(isGitRepository)) + .then(repos => { + if (!repos.every(result => !!result)) { + throw new Error( 'It appears that one of your testPathDirs does not exist ' + 'with in a git repository. Currently --onlyChanged only works ' + 'with git projects.\n' ); - /* eslint-enable no-throw-literal */ } - return Promise.all(config.testPathDirs.map(findChangedFiles)); + return Promise.all(Array.from(repos).map(findChangedFiles)); }) - .then(changedPathSets => { - // Collapse changed files from each of the testPathDirs into a single list - // of changed file paths - let changedPaths = []; - changedPathSets.forEach( - pathSet => changedPaths = changedPaths.concat(pathSet) - ); - return testRunner.promiseTestPathsRelatedTo(changedPaths); - }); + .then(changedPathSets => testRunner.promiseTestPathsRelatedTo( + new Set(Array.prototype.concat.apply([], changedPathSets)) + )); } function findMatchingTestPaths(argv, testRunner) {