From 3181917a36edd0bb3e24a5696d557d6c7c3ed43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Fri, 21 Sep 2018 15:33:18 +0100 Subject: [PATCH 1/6] Used relative paths in haste map --- e2e/__tests__/haste_map_sha1.test.js | 1 + .../__snapshots__/index.test.js.snap | 20 +- .../src/__tests__/index.test.js | 441 ++++++++++-------- .../src/__tests__/worker.test.js | 77 ++- .../src/crawlers/__tests__/node.test.js | 61 +-- .../src/crawlers/__tests__/watchman.test.js | 73 +-- packages/jest-haste-map/src/crawlers/node.js | 18 +- .../jest-haste-map/src/crawlers/watchman.js | 26 +- packages/jest-haste-map/src/haste_fs.js | 29 +- packages/jest-haste-map/src/index.js | 63 ++- packages/jest-haste-map/src/module_map.js | 26 +- packages/jest-haste-map/src/types.js | 2 + packages/jest-haste-map/src/worker.js | 9 +- .../src/__tests__/resolve.test.js | 12 +- packages/jest-runtime/src/index.js | 1 + types/HasteMap.js | 1 + 16 files changed, 513 insertions(+), 347 deletions(-) diff --git a/e2e/__tests__/haste_map_sha1.test.js b/e2e/__tests__/haste_map_sha1.test.js index e7f92c54d66f..9889a2e2b45c 100644 --- a/e2e/__tests__/haste_map_sha1.test.js +++ b/e2e/__tests__/haste_map_sha1.test.js @@ -40,6 +40,7 @@ test('exits the process after test are done but before timers complete', async ( name: 'tmp', platforms: ['ios', 'android'], retainAllFiles: true, + rootDir: DIR, roots: [DIR], useWatchman: false, watch: false, diff --git a/packages/jest-haste-map/src/__tests__/__snapshots__/index.test.js.snap b/packages/jest-haste-map/src/__tests__/__snapshots__/index.test.js.snap index 4e1c96ddd868..8122a04e6413 100644 --- a/packages/jest-haste-map/src/__tests__/__snapshots__/index.test.js.snap +++ b/packages/jest-haste-map/src/__tests__/__snapshots__/index.test.js.snap @@ -3,23 +3,23 @@ exports[`HasteMap file system changes processing recovery from duplicate module IDs recovers when the most recent duplicate is fixed 1`] = ` "The name \`Pear\` was looked up in the Haste module map. It cannot be resolved, because there exists several different files, or packages, that provide a module for that particular name and platform. The platform is generic (no extension). You must delete or blacklist files until there remains only one of these: - * \`/fruits/blueberry.js\` (module) - * \`/fruits/pear.js\` (module) + * \`/project/fruits/blueberry.js\` (module) + * \`/project/fruits/pear.js\` (module) " `; exports[`HasteMap file system changes processing recovery from duplicate module IDs recovers when the oldest version of the duplicates is fixed 1`] = ` "The name \`Pear\` was looked up in the Haste module map. It cannot be resolved, because there exists several different files, or packages, that provide a module for that particular name and platform. The platform is generic (no extension). You must delete or blacklist files until there remains only one of these: - * \`/fruits/blueberry.js\` (module) - * \`/fruits/pear.js\` (module) + * \`/project/fruits/blueberry.js\` (module) + * \`/project/fruits/pear.js\` (module) " `; exports[`HasteMap throws on duplicate module ids if "throwOnModuleCollision" is set to true 1`] = ` [Error: jest-haste-map: @providesModule naming collision: Duplicate module name: Strawberry - Paths: /fruits/raspberry.js collides with /fruits/strawberry.js + Paths: /project/fruits/raspberry.js collides with /project/fruits/strawberry.js This error is caused by a @providesModule declaration with the same name across two different files.] `; @@ -33,13 +33,13 @@ exports[`HasteMap tries to crawl using node as a fallback 1`] = ` exports[`HasteMap warns on duplicate mock files 1`] = ` "jest-haste-map: duplicate manual mock found: Module name: subdir/blueberry - Duplicate Mock path: /fruits2/__mocks__/subdir/blueberry.js + Duplicate Mock path: /project/fruits2/__mocks__/subdir/blueberry.js This warning is caused by two manual mock files with the same file name. Jest will use the mock file found in: -/fruits2/__mocks__/subdir/blueberry.js +/project/fruits2/__mocks__/subdir/blueberry.js Please delete one of the following two files: - /fruits1/__mocks__/subdir/blueberry.js -/fruits2/__mocks__/subdir/blueberry.js + /project/fruits1/__mocks__/subdir/blueberry.js +/project/fruits2/__mocks__/subdir/blueberry.js " `; @@ -47,7 +47,7 @@ Jest will use the mock file found in: exports[`HasteMap warns on duplicate module ids 1`] = ` "jest-haste-map: @providesModule naming collision: Duplicate module name: Strawberry - Paths: /fruits/raspberry.js collides with /fruits/strawberry.js + Paths: /project/fruits/raspberry.js collides with /project/fruits/strawberry.js This warning is caused by a @providesModule declaration with the same name across two different files." `; diff --git a/packages/jest-haste-map/src/__tests__/index.test.js b/packages/jest-haste-map/src/__tests__/index.test.js index 8afc717dc2d1..f9a8633275a6 100644 --- a/packages/jest-haste-map/src/__tests__/index.test.js +++ b/packages/jest-haste-map/src/__tests__/index.test.js @@ -37,19 +37,22 @@ jest.mock('jest-worker', () => jest.mock('../crawlers/node'); jest.mock('../crawlers/watchman', () => jest.fn(options => { - const {data, ignore, roots, computeSha1} = options; + const path = require('path'); + + const {data, ignore, rootDir, roots, computeSha1} = options; const list = mockChangedFiles || mockFs; data.clocks = mockClocks; for (const file in list) { if (new RegExp(roots.join('|')).test(file) && !ignore(file)) { + const relativeFilePath = path.relative(rootDir, file); if (list[file]) { const hash = computeSha1 ? mockHashContents(list[file]) : null; - data.files.set(file, ['', 32, 0, [], hash]); + data.files.set(relativeFilePath, ['', 32, 0, [], hash]); } else { - data.files.delete(file); + data.files.delete(relativeFilePath); } } } @@ -139,37 +142,46 @@ describe('HasteMap', () => { mockEmitters = Object.create(null); mockFs = object({ - '/fruits/__mocks__/Pear.js': ['const Melon = require("Melon");'].join( - '\n', - ), - '/fruits/banana.js': [ + '/project/fruits/__mocks__/Pear.js': [ + 'const Melon = require("Melon");', + ].join('\n'), + '/project/fruits/banana.js': [ '/**', ' * @providesModule Banana', ' */', 'const Strawberry = require("Strawberry");', ].join('\n'), - '/fruits/kiwi.js': ['/**', ' * @providesModule Kiwi', ' */'].join('\n'), - '/fruits/pear.js': [ + '/project/fruits/kiwi.js': ['/**', ' * @providesModule Kiwi', ' */'].join( + '\n', + ), + '/project/fruits/pear.js': [ '/**', ' * @providesModule Pear', ' */', 'const Banana = require("Banana");', 'const Strawberry = require("Strawberry");', ].join('\n'), - '/fruits/strawberry.js': [ + '/project/fruits/strawberry.js': [ '/**', ' * @providesModule Strawberry', ' */', ].join('\n'), - '/vegetables/melon.js': ['/**', ' * @providesModule Melon', ' */'].join( - '\n', - ), - '/video/video.mp4': Buffer.from([0xfa, 0xce, 0xb0, 0x0c]).toString(), + '/project/vegetables/melon.js': [ + '/**', + ' * @providesModule Melon', + ' */', + ].join('\n'), + '/project/video/video.mp4': Buffer.from([ + 0xfa, + 0xce, + 0xb0, + 0x0c, + ]).toString(), }); mockClocks = createMap({ - '/fruits': 'c:fake-clock:1', - '/vegetables': 'c:fake-clock:2', - '/video': 'c:fake-clock:3', + fruits: 'c:fake-clock:1', + vegetables: 'c:fake-clock:2', + video: 'c:fake-clock:3', }); mockChangedFiles = null; @@ -192,7 +204,8 @@ describe('HasteMap', () => { name: 'haste-map-test', platforms: ['ios', 'android'], resetCache: false, - roots: ['/fruits', '/vegetables'], + rootDir: '/project', + roots: ['/project/fruits', '/project/vegetables'], useWatchman: true, }; }); @@ -222,55 +235,51 @@ describe('HasteMap', () => { it('matches files against a pattern', () => new HasteMap(defaultConfig).build().then(({hasteFS}) => { - expect(hasteFS.matchFiles(/fruits/)).toEqual([ - '/fruits/__mocks__/Pear.js', - '/fruits/banana.js', - '/fruits/pear.js', - '/fruits/strawberry.js', + expect(hasteFS.matchFiles(/project\/fruits/)).toEqual([ + '/project/fruits/__mocks__/Pear.js', + '/project/fruits/banana.js', + '/project/fruits/pear.js', + '/project/fruits/strawberry.js', ]); expect(hasteFS.matchFiles(/__mocks__/)).toEqual([ - '/fruits/__mocks__/Pear.js', + '/project/fruits/__mocks__/Pear.js', ]); })); it('builds a haste map on a fresh cache', () => { // Include these files in the map - mockFs['/fruits/node_modules/react/react.js'] = [ + mockFs['/project/fruits/node_modules/react/react.js'] = [ '/**', ' * @providesModule React', ' */', 'const Component = require("Component");', ].join('\n'); - mockFs['/fruits/node_modules/fbjs/lib/flatMap.js'] = [ + mockFs['/project/fruits/node_modules/fbjs/lib/flatMap.js'] = [ '/**', ' * @providesModule flatMap', ' */', ].join('\n'); // Ignore these - mockFs['/fruits/node_modules/react/node_modules/fbjs/lib/mapObject.js'] = [ - '/**', - ' * @providesModule mapObject', - ' */', - ].join('\n'); - mockFs['/fruits/node_modules/react/node_modules/dummy/merge.js'] = [ + mockFs[ + '/project/fruits/node_modules/react/node_modules/fbjs/lib/mapObject.js' + ] = ['/**', ' * @providesModule mapObject', ' */'].join('\n'); + mockFs['/project/fruits/node_modules/react/node_modules/dummy/merge.js'] = [ '/**', ' * @providesModule merge', ' */', ].join('\n'); - mockFs['/fruits/node_modules/react/node_modules/merge/package.json'] = [ - '{', - ' "name": "merge"', - '}', - ].join('\n'); - mockFs['/fruits/node_modules/jest/jest.js'] = [ + mockFs[ + '/project/fruits/node_modules/react/node_modules/merge/package.json' + ] = ['{', ' "name": "merge"', '}'].join('\n'); + mockFs['/project/fruits/node_modules/jest/jest.js'] = [ '/**', ' * @providesModule Jest', ' */', 'const Test = require("Test");', ].join('\n'); - mockFs['/fruits/node_modules/fbjs2/index.js'] = [ + mockFs['/project/fruits/node_modules/fbjs2/index.js'] = [ '/**', ' * @providesModule fbjs2', ' */', @@ -288,17 +297,17 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - '/fruits/__mocks__/Pear.js': ['', 32, 1, ['Melon'], null], - '/fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], + 'fruits/__mocks__/Pear.js': ['', 32, 1, ['Melon'], null], + 'fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], // node modules - '/fruits/node_modules/fbjs/lib/flatMap.js': [ + 'fruits/node_modules/fbjs/lib/flatMap.js': [ 'flatMap', 32, 1, [], null, ], - '/fruits/node_modules/react/react.js': [ + 'fruits/node_modules/react/react.js': [ 'React', 32, 1, @@ -306,29 +315,29 @@ describe('HasteMap', () => { null, ], - '/fruits/pear.js': ['Pear', 32, 1, ['Banana', 'Strawberry'], null], - '/fruits/strawberry.js': ['Strawberry', 32, 1, [], null], - '/vegetables/melon.js': ['Melon', 32, 1, [], null], + 'fruits/pear.js': ['Pear', 32, 1, ['Banana', 'Strawberry'], null], + 'fruits/strawberry.js': ['Strawberry', 32, 1, [], null], + 'vegetables/melon.js': ['Melon', 32, 1, [], null], }), ); expect(data.map).toEqual( createMap({ - Banana: {[H.GENERIC_PLATFORM]: ['/fruits/banana.js', H.MODULE]}, - Melon: {[H.GENERIC_PLATFORM]: ['/vegetables/melon.js', H.MODULE]}, - Pear: {[H.GENERIC_PLATFORM]: ['/fruits/pear.js', H.MODULE]}, + Banana: {[H.GENERIC_PLATFORM]: ['fruits/banana.js', H.MODULE]}, + Melon: {[H.GENERIC_PLATFORM]: ['vegetables/melon.js', H.MODULE]}, + Pear: {[H.GENERIC_PLATFORM]: ['fruits/pear.js', H.MODULE]}, React: { [H.GENERIC_PLATFORM]: [ - '/fruits/node_modules/react/react.js', + 'fruits/node_modules/react/react.js', H.MODULE, ], }, Strawberry: { - [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE], + [H.GENERIC_PLATFORM]: ['fruits/strawberry.js', H.MODULE], }, flatMap: { [H.GENERIC_PLATFORM]: [ - '/fruits/node_modules/fbjs/lib/flatMap.js', + 'fruits/node_modules/fbjs/lib/flatMap.js', H.MODULE, ], }, @@ -337,7 +346,7 @@ describe('HasteMap', () => { expect(data.mocks).toEqual( createMap({ - Pear: '/fruits/__mocks__/Pear.js', + Pear: 'fruits/__mocks__/Pear.js', }), ); @@ -357,11 +366,11 @@ describe('HasteMap', () => { // The node crawler returns "null" for the SHA-1. data.files = createMap({ - '/fruits/__mocks__/Pear.js': ['', 32, 0, ['Melon'], null], - '/fruits/banana.js': ['Banana', 32, 0, ['Strawberry'], null], - '/fruits/pear.js': ['Pear', 32, 0, ['Banana', 'Strawberry'], null], - '/fruits/strawberry.js': ['Strawberry', 32, 0, [], null], - '/vegetables/melon.js': ['Melon', 32, 0, [], null], + 'fruits/__mocks__/Pear.js': ['', 32, 0, ['Melon'], null], + 'fruits/banana.js': ['Banana', 32, 0, ['Strawberry'], null], + 'fruits/pear.js': ['Pear', 32, 0, ['Banana', 'Strawberry'], null], + 'fruits/strawberry.js': ['Strawberry', 32, 0, [], null], + 'vegetables/melon.js': ['Melon', 32, 0, [], null], }); return Promise.resolve(data); @@ -379,35 +388,35 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - '/fruits/__mocks__/Pear.js': [ + 'fruits/__mocks__/Pear.js': [ '', 32, 1, ['Melon'], 'a315b7804be2b124b77c1f107205397f45226964', ], - '/fruits/banana.js': [ + 'fruits/banana.js': [ 'Banana', 32, 1, ['Strawberry'], 'f24c6984cce6f032f6d55d771d04ab8dbbe63c8c', ], - '/fruits/pear.js': [ + 'fruits/pear.js': [ 'Pear', 32, 1, ['Banana', 'Strawberry'], '211a8ff1e67007b204727d26943c15cf9fd00031', ], - '/fruits/strawberry.js': [ + 'fruits/strawberry.js': [ 'Strawberry', 32, 1, [], 'd55d545ad7d997cb2aa10fb412e0cc287d4fbfb3', ], - '/vegetables/melon.js': [ + 'vegetables/melon.js': [ 'Melon', 32, 1, @@ -423,7 +432,7 @@ describe('HasteMap', () => { }); it('does not crawl native files even if requested to do so', async () => { - mockFs['/video/i-require-a-video.js'] = [ + mockFs['/project/video/i-require-a-video.js'] = [ '/**', ' * @providesModule IRequireAVideo', ' */', @@ -433,19 +442,19 @@ describe('HasteMap', () => { const hasteMap = new HasteMap( Object.assign({}, defaultConfig, { extensions: [...defaultConfig.extensions], - roots: [...defaultConfig.roots, '/video'], + roots: [...defaultConfig.roots, '/project/video'], }), ); const {__hasteMapForTest: data} = await hasteMap.build(); expect(data.map.get('IRequireAVideo')).toBeDefined(); - expect(data.files.get('/video/video.mp4')).toBeDefined(); - expect(fs.readFileSync).not.toBeCalledWith('/video/video.mp4', 'utf8'); + expect(data.files.get('video/video.mp4')).toBeDefined(); + expect(fs.readFileSync).not.toBeCalledWith('video/video.mp4', 'utf8'); }); it('retains all files if `retainAllFiles` is specified', () => { - mockFs['/fruits/node_modules/fbjs/index.js'] = [ + mockFs['/project/fruits/node_modules/fbjs/index.js'] = [ '/**', ' * @providesModule fbjs', ' */', @@ -461,7 +470,7 @@ describe('HasteMap', () => { return hasteMap.build().then(({__hasteMapForTest: data}) => { // Expect the node module to be part of files but make sure it wasn't // read. - expect(data.files.get('/fruits/node_modules/fbjs/index.js')).toEqual([ + expect(data.files.get('fruits/node_modules/fbjs/index.js')).toEqual([ '', 32, 0, @@ -478,12 +487,12 @@ describe('HasteMap', () => { it('warns on duplicate mock files', () => { // Duplicate mock files for blueberry - mockFs['/fruits1/__mocks__/subdir/blueberry.js'] = [ + mockFs['/project/fruits1/__mocks__/subdir/blueberry.js'] = [ '/**', ' * @providesModule Blueberry1', ' */', ].join('\n'); - mockFs['/fruits2/__mocks__/subdir/blueberry.js'] = [ + mockFs['/project/fruits2/__mocks__/subdir/blueberry.js'] = [ '/**', ' * @providesModule Blueberry2', ' */', @@ -500,7 +509,7 @@ describe('HasteMap', () => { it('warns on duplicate module ids', () => { // Raspberry thinks it is a Strawberry - mockFs['/fruits/raspberry.js'] = [ + mockFs['/project/fruits/raspberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -522,7 +531,7 @@ describe('HasteMap', () => { it('throws on duplicate module ids if "throwOnModuleCollision" is set to true', () => { // Raspberry thinks it is a Strawberry - mockFs['/fruits/raspberry.js'] = [ + mockFs['/project/fruits/raspberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -540,21 +549,21 @@ describe('HasteMap', () => { it('splits up modules by platform', () => { mockFs = Object.create(null); - mockFs['/fruits/strawberry.js'] = [ + mockFs['/project/fruits/strawberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', 'const Banana = require("Banana");', ].join('\n'); - mockFs['/fruits/strawberry.ios.js'] = [ + mockFs['/project/fruits/strawberry.ios.js'] = [ '/**', ' * @providesModule Strawberry', ' */', 'const Raspberry = require("Raspberry");', ].join('\n'); - mockFs['/fruits/strawberry.android.js'] = [ + mockFs['/project/fruits/strawberry.android.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -566,30 +575,30 @@ describe('HasteMap', () => { .then(({__hasteMapForTest: data}) => { expect(data.files).toEqual( createMap({ - '/fruits/strawberry.android.js': [ + 'fruits/strawberry.android.js': [ 'Strawberry', 32, 1, ['Blackberry'], null, ], - '/fruits/strawberry.ios.js': [ + 'fruits/strawberry.ios.js': [ 'Strawberry', 32, 1, ['Raspberry'], null, ], - '/fruits/strawberry.js': ['Strawberry', 32, 1, ['Banana'], null], + 'fruits/strawberry.js': ['Strawberry', 32, 1, ['Banana'], null], }), ); expect(data.map).toEqual( createMap({ Strawberry: { - [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE], - android: ['/fruits/strawberry.android.js', H.MODULE], - ios: ['/fruits/strawberry.ios.js', H.MODULE], + [H.GENERIC_PLATFORM]: ['fruits/strawberry.js', H.MODULE], + android: ['fruits/strawberry.android.js', H.MODULE], + ios: ['fruits/strawberry.ios.js', H.MODULE], }, }), ); @@ -611,8 +620,8 @@ describe('HasteMap', () => { // Watchman would give us different clocks. mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:4', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:4', }); return new HasteMap(defaultConfig) @@ -638,7 +647,7 @@ describe('HasteMap', () => { // Let's assume one JS file has changed. mockChangedFiles = object({ - '/fruits/banana.js': [ + '/project/fruits/banana.js': [ '/**', ' * @providesModule Kiwi', // Identity crisis. ' */', @@ -646,10 +655,10 @@ describe('HasteMap', () => { ].join('\n'), }); - // Watchman would give us different clocks for `/fruits`. + // Watchman would give us different clocks for `/project/fruits`. mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:2', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:2', }); return new HasteMap(defaultConfig) @@ -662,18 +671,15 @@ describe('HasteMap', () => { } else { expect(fs.readFileSync).toBeCalledWith(cacheFilePath, 'utf8'); } - expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8'); + expect(fs.readFileSync).toBeCalledWith( + '/project/fruits/banana.js', + 'utf8', + ); expect(normalizeMap(data.clocks)).toEqual(mockClocks); const files = new Map(initialData.files); - files.set('/fruits/banana.js', [ - 'Kiwi', - 32, - 1, - ['Raspberry'], - null, - ]); + files.set('fruits/banana.js', ['Kiwi', 32, 1, ['Raspberry'], null]); expect(normalizeMap(data.files)).toEqual(files); @@ -692,22 +698,22 @@ describe('HasteMap', () => { fs.readFileSync.mockClear(); // Let's assume one JS file was removed. - delete mockFs['/fruits/banana.js']; + delete mockFs['/project/fruits/banana.js']; mockChangedFiles = object({ - '/fruits/banana.js': null, + '/project/fruits/banana.js': null, }); - // Watchman would give us different clocks for `/fruits`. + // Watchman would give us different clocks for `/project/fruits`. mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:2', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:2', }); return new HasteMap(defaultConfig) .build() .then(({__hasteMapForTest: data}) => { const files = new Map(initialData.files); - files.delete('/fruits/banana.js'); + files.delete('fruits/banana.js'); expect(normalizeMap(data.files)).toEqual(files); const map = new Map(initialData.map); @@ -718,7 +724,7 @@ describe('HasteMap', () => { it('correctly handles platform-specific file additions', async () => { mockFs = Object.create(null); - mockFs['/fruits/strawberry.js'] = [ + mockFs['/project/fruits/strawberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -727,35 +733,35 @@ describe('HasteMap', () => { let data; ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], + g: ['fruits/strawberry.js', 0], }); - delete mockFs['/fruits/strawberry.ios.js']; + delete mockFs['/project/fruits/strawberry.ios.js']; mockChangedFiles = object({ - '/fruits/strawberry.ios.js': [ + '/project/fruits/strawberry.ios.js': [ '/**', ' * @providesModule Strawberry', ' */', 'const Raspberry = require("Raspberry");', ].join('\n'), }); - mockClocks = createMap({'/fruits': 'c:fake-clock:3'}); + mockClocks = createMap({fruits: 'c:fake-clock:3'}); ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], - ios: ['/fruits/strawberry.ios.js', 0], + g: ['fruits/strawberry.js', 0], + ios: ['fruits/strawberry.ios.js', 0], }); }); it('correctly handles platform-specific file deletions', async () => { mockFs = Object.create(null); - mockFs['/fruits/strawberry.js'] = [ + mockFs['/project/fruits/strawberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', 'const Banana = require("Banana");', ].join('\n'); - mockFs['/fruits/strawberry.ios.js'] = [ + mockFs['/project/fruits/strawberry.ios.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -764,22 +770,22 @@ describe('HasteMap', () => { let data; ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], - ios: ['/fruits/strawberry.ios.js', 0], + g: ['fruits/strawberry.js', 0], + ios: ['fruits/strawberry.ios.js', 0], }); - delete mockFs['/fruits/strawberry.ios.js']; - mockChangedFiles = object({'/fruits/strawberry.ios.js': null}); - mockClocks = createMap({'/fruits': 'c:fake-clock:3'}); + delete mockFs['/project/fruits/strawberry.ios.js']; + mockChangedFiles = object({'/project/fruits/strawberry.ios.js': null}); + mockClocks = createMap({fruits: 'c:fake-clock:3'}); ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], + g: ['fruits/strawberry.js', 0], }); }); it('correctly handles platform-specific file renames', async () => { mockFs = Object.create(null); - mockFs['/fruits/strawberry.ios.js'] = [ + mockFs['/project/fruits/strawberry.ios.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -788,29 +794,29 @@ describe('HasteMap', () => { let data; ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - ios: ['/fruits/strawberry.ios.js', 0], + ios: ['fruits/strawberry.ios.js', 0], }); - delete mockFs['/fruits/strawberry.ios.js']; + delete mockFs['/project/fruits/strawberry.ios.js']; mockChangedFiles = object({ - '/fruits/strawberry.ios.js': null, - '/fruits/strawberry.js': [ + '/project/fruits/strawberry.ios.js': null, + '/project/fruits/strawberry.js': [ '/**', ' * @providesModule Strawberry', ' */', 'const Banana = require("Banana");', ].join('\n'), }); - mockClocks = createMap({'/fruits': 'c:fake-clock:3'}); + mockClocks = createMap({fruits: 'c:fake-clock:3'}); ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], + g: ['fruits/strawberry.js', 0], }); }); describe('duplicate modules', () => { beforeEach(async () => { - mockFs['/fruits/another_strawberry.js'] = [ + mockFs['/project/fruits/another_strawberry.js'] = [ '/**', ' * @providesModule Strawberry', ' */', @@ -823,7 +829,7 @@ describe('HasteMap', () => { expect(normalizeMap(data.duplicates)).toEqual( createMap({ Strawberry: { - g: {'/fruits/another_strawberry.js': 0, '/fruits/strawberry.js': 0}, + g: {'fruits/another_strawberry.js': 0, 'fruits/strawberry.js': 0}, }, }), ); @@ -831,13 +837,13 @@ describe('HasteMap', () => { }); it('recovers when a duplicate file is deleted', async () => { - delete mockFs['/fruits/another_strawberry.js']; + delete mockFs['/project/fruits/another_strawberry.js']; mockChangedFiles = object({ - '/fruits/another_strawberry.js': null, + '/project/fruits/another_strawberry.js': null, }); mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:2', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:2', }); const {__hasteMapForTest: data} = await new HasteMap( @@ -845,15 +851,15 @@ describe('HasteMap', () => { ).build(); expect(normalizeMap(data.duplicates)).toEqual(new Map()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], + g: ['fruits/strawberry.js', 0], }); // Make sure the other files are not affected. - expect(data.map.get('Banana')).toEqual({g: ['/fruits/banana.js', 0]}); + expect(data.map.get('Banana')).toEqual({g: ['fruits/banana.js', 0]}); }); it('recovers when a duplicate module is renamed', async () => { mockChangedFiles = object({ - '/fruits/another_strawberry.js': [ + '/project/fruits/another_strawberry.js': [ '/**', ' * @providesModule AnotherStrawberry', ' */', @@ -861,8 +867,8 @@ describe('HasteMap', () => { ].join('\n'), }); mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:2', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:2', }); const {__hasteMapForTest: data} = await new HasteMap( @@ -870,13 +876,13 @@ describe('HasteMap', () => { ).build(); expect(normalizeMap(data.duplicates)).toEqual(new Map()); expect(data.map.get('Strawberry')).toEqual({ - g: ['/fruits/strawberry.js', 0], + g: ['fruits/strawberry.js', 0], }); expect(data.map.get('AnotherStrawberry')).toEqual({ - g: ['/fruits/another_strawberry.js', 0], + g: ['fruits/another_strawberry.js', 0], }); // Make sure the other files are not affected. - expect(data.map.get('Banana')).toEqual({g: ['/fruits/banana.js', 0]}); + expect(data.map.get('Banana')).toEqual({g: ['fruits/banana.js', 0]}); }); }); @@ -890,8 +896,8 @@ describe('HasteMap', () => { // Watchman would give us different clocks. mockClocks = createMap({ - '/fruits': 'c:fake-clock:3', - '/vegetables': 'c:fake-clock:4', + fruits: 'c:fake-clock:3', + vegetables: 'c:fake-clock:4', }); const config = Object.assign({}, defaultConfig, { @@ -910,7 +916,7 @@ describe('HasteMap', () => { watchman.mockImplementation(options => mockImpl(options).then(() => { const {data} = options; - data.files.set('/fruits/invalid/file.js', ['', 34, 0, []]); + data.files.set('fruits/invalid/file.js', ['', 34, 0, []]); return data; }), ); @@ -920,7 +926,7 @@ describe('HasteMap', () => { expect(data.files.size).toBe(5); // Ensure this file is not part of the file list. - expect(data.files.get('/fruits/invalid/file.js')).toBe(undefined); + expect(data.files.get('fruits/invalid/file.js')).toBe(undefined); }); }); @@ -942,40 +948,45 @@ describe('HasteMap', () => { { computeDependencies: true, computeSha1: false, - filePath: '/fruits/__mocks__/Pear.js', + filePath: '/project/fruits/__mocks__/Pear.js', hasteImplModulePath: undefined, + rootDir: '/project', }, ], [ { computeDependencies: true, computeSha1: false, - filePath: '/fruits/banana.js', + filePath: '/project/fruits/banana.js', hasteImplModulePath: undefined, + rootDir: '/project', }, ], [ { computeDependencies: true, computeSha1: false, - filePath: '/fruits/pear.js', + filePath: '/project/fruits/pear.js', hasteImplModulePath: undefined, + rootDir: '/project', }, ], [ { computeDependencies: true, computeSha1: false, - filePath: '/fruits/strawberry.js', + filePath: '/project/fruits/strawberry.js', hasteImplModulePath: undefined, + rootDir: '/project', }, ], [ { computeDependencies: true, computeSha1: false, - filePath: '/vegetables/melon.js', + filePath: '/project/vegetables/melon.js', hasteImplModulePath: undefined, + rootDir: '/project', }, ], ]); @@ -994,7 +1005,7 @@ describe('HasteMap', () => { node.mockImplementation(options => { const {data} = options; data.files = createMap({ - '/fruits/banana.js': ['', 32, 0, [], null], + 'fruits/banana.js': ['', 32, 0, [], null], }); return Promise.resolve(data); }); @@ -1007,7 +1018,7 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - '/fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], + 'fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], }), ); @@ -1025,7 +1036,7 @@ describe('HasteMap', () => { node.mockImplementation(options => { const {data} = options; data.files = createMap({ - '/fruits/banana.js': ['', 32, 0, [], null], + 'fruits/banana.js': ['', 32, 0, [], null], }); return Promise.resolve(data); }); @@ -1038,7 +1049,7 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - '/fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], + 'fruits/banana.js': ['Banana', 32, 1, ['Strawberry'], null], }), ); }); @@ -1099,11 +1110,11 @@ describe('HasteMap', () => { hm_it('provides a new set of hasteHS and moduleMap', async hm => { const initialResult = await hm.build(); - const filePath = '/fruits/banana.js'; + const filePath = '/project/fruits/banana.js'; expect(initialResult.hasteFS.getModuleName(filePath)).toBeDefined(); expect(initialResult.moduleMap.getModule('Banana')).toBe(filePath); - mockDeleteFile('/fruits', 'banana.js'); - mockDeleteFile('/fruits', 'banana.js'); + mockDeleteFile('/project/fruits', 'banana.js'); + mockDeleteFile('/project/fruits', 'banana.js'); const {eventsQueue, hasteFS, moduleMap} = await waitForItToChange(hm); expect(eventsQueue).toHaveLength(1); const deletedBanana = {filePath, stat: undefined, type: 'delete'}; @@ -1126,42 +1137,42 @@ describe('HasteMap', () => { }; hm_it('handles several change events at once', async hm => { - mockFs['/fruits/tomato.js'] = [ + mockFs['/project/fruits/tomato.js'] = [ '/**', ' * @providesModule Tomato', ' */', ].join('\n'); - mockFs['/fruits/pear.js'] = [ + mockFs['/project/fruits/pear.js'] = [ '/**', ' * @providesModule Kiwi', ' */', ].join('\n'); - const e = mockEmitters['/fruits']; - e.emit('all', 'add', 'tomato.js', '/fruits', MOCK_STAT_FILE); - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit('all', 'add', 'tomato.js', '/project/fruits', MOCK_STAT_FILE); + e.emit('all', 'change', 'pear.js', '/project/fruits', MOCK_STAT_FILE); const {eventsQueue, hasteFS, moduleMap} = await waitForItToChange(hm); expect(eventsQueue).toEqual([ { - filePath: '/fruits/tomato.js', + filePath: '/project/fruits/tomato.js', stat: MOCK_STAT_FILE, type: 'add', }, { - filePath: '/fruits/pear.js', + filePath: '/project/fruits/pear.js', stat: MOCK_STAT_FILE, type: 'change', }, ]); - expect(hasteFS.getModuleName('/fruits/tomato.js')).not.toBeNull(); + expect(hasteFS.getModuleName('/project/fruits/tomato.js')).not.toBeNull(); expect(moduleMap.getModule('Tomato')).toBeDefined(); expect(moduleMap.getModule('Pear')).toBeNull(); - expect(moduleMap.getModule('Kiwi')).toBe('/fruits/pear.js'); + expect(moduleMap.getModule('Kiwi')).toBe('/project/fruits/pear.js'); }); hm_it('does not emit duplicate change events', async hm => { - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FILE); - e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit('all', 'change', 'tomato.js', '/project/fruits', MOCK_STAT_FILE); + e.emit('all', 'change', 'tomato.js', '/project/fruits', MOCK_STAT_FILE); const {eventsQueue} = await waitForItToChange(hm); expect(eventsQueue).toHaveLength(1); }); @@ -1169,16 +1180,16 @@ describe('HasteMap', () => { hm_it( 'emits a change even if a file in node_modules has changed', async hm => { - const e = mockEmitters['/fruits']; + const e = mockEmitters['/project/fruits']; e.emit( 'all', 'add', 'apple.js', - '/fruits/node_modules/', + '/project/fruits/node_modules/', MOCK_STAT_FILE, ); const {eventsQueue, hasteFS} = await waitForItToChange(hm); - const filePath = '/fruits/node_modules/apple.js'; + const filePath = '/project/fruits/node_modules/apple.js'; expect(eventsQueue).toHaveLength(1); expect(eventsQueue).toEqual([ {filePath, stat: MOCK_STAT_FILE, type: 'add'}, @@ -1193,45 +1204,55 @@ describe('HasteMap', () => { const {moduleMap: initMM} = await hm.build(); expect(initMM.getModule('Orange', 'ios')).toBeTruthy(); expect(initMM.getModule('Orange', 'android')).toBeTruthy(); - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'Orange.ios.js', '/fruits/', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit( + 'all', + 'change', + 'Orange.ios.js', + '/project/fruits/', + MOCK_STAT_FILE, + ); e.emit( 'all', 'change', 'Orange.android.js', - '/fruits/', + '/project/fruits/', MOCK_STAT_FILE, ); const {eventsQueue, hasteFS, moduleMap} = await waitForItToChange(hm); expect(eventsQueue).toHaveLength(2); expect(eventsQueue).toEqual([ { - filePath: '/fruits/Orange.ios.js', + filePath: '/project/fruits/Orange.ios.js', stat: MOCK_STAT_FILE, type: 'change', }, { - filePath: '/fruits/Orange.android.js', + filePath: '/project/fruits/Orange.android.js', stat: MOCK_STAT_FILE, type: 'change', }, ]); - expect(hasteFS.getModuleName('/fruits/Orange.ios.js')).toBeTruthy(); - expect(hasteFS.getModuleName('/fruits/Orange.android.js')).toBeTruthy(); + expect( + hasteFS.getModuleName('/project/fruits/Orange.ios.js'), + ).toBeTruthy(); + expect( + hasteFS.getModuleName('/project/fruits/Orange.android.js'), + ).toBeTruthy(); const iosVariant = moduleMap.getModule('Orange', 'ios'); - expect(iosVariant).toBe('/fruits/Orange.ios.js'); + expect(iosVariant).toBe('/project/fruits/Orange.ios.js'); const androidVariant = moduleMap.getModule('Orange', 'android'); - expect(androidVariant).toBe('/fruits/Orange.android.js'); + expect(androidVariant).toBe('/project/fruits/Orange.android.js'); }, { mockFs: { - '/fruits/Orange.android.js': [ + '/project/fruits/Orange.android.js': [ '/**', ' * @providesModule Orange', ' */', ].join('\n'), - '/fruits/Orange.ios.js': [ + '/project/fruits/Orange.ios.js': [ '/**', ' * @providesModule Orange', ' */', @@ -1242,21 +1263,21 @@ describe('HasteMap', () => { describe('recovery from duplicate module IDs', () => { async function setupDuplicates(hm) { - mockFs['/fruits/pear.js'] = [ + mockFs['/project/fruits/pear.js'] = [ '/**', ' * @providesModule Pear', ' */', ].join('\n'); - mockFs['/fruits/blueberry.js'] = [ + mockFs['/project/fruits/blueberry.js'] = [ '/**', ' * @providesModule Pear', ' */', ].join('\n'); - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); - e.emit('all', 'add', 'blueberry.js', '/fruits', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit('all', 'change', 'pear.js', '/project/fruits', MOCK_STAT_FILE); + e.emit('all', 'add', 'blueberry.js', '/project/fruits', MOCK_STAT_FILE); const {hasteFS, moduleMap} = await waitForItToChange(hm); - expect(hasteFS.exists('/fruits/blueberry.js')).toBe(true); + expect(hasteFS.exists('/project/fruits/blueberry.js')).toBe(true); try { moduleMap.getModule('Pear'); throw new Error('should be unreachable'); @@ -1269,8 +1290,8 @@ describe('HasteMap', () => { expect(error.platform).toBe('g'); expect(error.supportsNativePlatform).toBe(false); expect(error.duplicatesSet).toEqual({ - '/fruits/blueberry.js': 0, - '/fruits/pear.js': 0, + '/project/fruits/blueberry.js': 0, + '/project/fruits/pear.js': 0, }); expect(error.message).toMatchSnapshot(); } @@ -1280,42 +1301,60 @@ describe('HasteMap', () => { 'recovers when the oldest version of the duplicates is fixed', async hm => { await setupDuplicates(hm); - mockFs['/fruits/pear.js'] = [ + mockFs['/project/fruits/pear.js'] = [ '/**', ' * @providesModule OldPear', ' */', ].join('\n'); - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit('all', 'change', 'pear.js', '/project/fruits', MOCK_STAT_FILE); const {moduleMap} = await waitForItToChange(hm); - expect(moduleMap.getModule('Pear')).toBe('/fruits/blueberry.js'); - expect(moduleMap.getModule('OldPear')).toBe('/fruits/pear.js'); + expect(moduleMap.getModule('Pear')).toBe( + '/project/fruits/blueberry.js', + ); + expect(moduleMap.getModule('OldPear')).toBe( + '/project/fruits/pear.js', + ); expect(moduleMap.getModule('Blueberry')).toBe(null); }, ); hm_it('recovers when the most recent duplicate is fixed', async hm => { await setupDuplicates(hm); - mockFs['/fruits/blueberry.js'] = [ + mockFs['/project/fruits/blueberry.js'] = [ '/**', ' * @providesModule Blueberry', ' */', ].join('\n'); - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'blueberry.js', '/fruits', MOCK_STAT_FILE); + const e = mockEmitters['/project/fruits']; + e.emit( + 'all', + 'change', + 'blueberry.js', + '/project/fruits', + MOCK_STAT_FILE, + ); const {moduleMap} = await waitForItToChange(hm); - expect(moduleMap.getModule('Pear')).toBe('/fruits/pear.js'); - expect(moduleMap.getModule('Blueberry')).toBe('/fruits/blueberry.js'); + expect(moduleMap.getModule('Pear')).toBe('/project/fruits/pear.js'); + expect(moduleMap.getModule('Blueberry')).toBe( + '/project/fruits/blueberry.js', + ); }); hm_it('ignore directories', async hm => { - const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FOLDER); + const e = mockEmitters['/project/fruits']; + e.emit( + 'all', + 'change', + 'tomato.js', + '/project/fruits', + MOCK_STAT_FOLDER, + ); e.emit( 'all', 'change', 'tomato.js', - '/fruits/tomato.js/index.js', + '/project/fruits/tomato.js/index.js', MOCK_STAT_FILE, ); const {eventsQueue} = await waitForItToChange(hm); diff --git a/packages/jest-haste-map/src/__tests__/worker.test.js b/packages/jest-haste-map/src/__tests__/worker.test.js index e2637d9860aa..bda4dfa8c99b 100644 --- a/packages/jest-haste-map/src/__tests__/worker.test.js +++ b/packages/jest-haste-map/src/__tests__/worker.test.js @@ -16,6 +16,7 @@ import H from '../constants'; const {worker, getSha1} = require('../worker'); +const rootDir = '/project'; let mockFs; let readFileSync; let readFile; @@ -25,26 +26,35 @@ describe('worker', () => { beforeEach(() => { mockFs = { - '/fruits/apple.png': Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), - '/fruits/banana.js': [ + '/project/fruits/apple.png': Buffer.from([ + 137, + 80, + 78, + 71, + 13, + 10, + 26, + 10, + ]), + '/project/fruits/banana.js': [ '/**', ' * @providesModule Banana', ' */', 'const Strawberry = require("Strawberry");', ].join('\n'), - '/fruits/pear.js': [ + '/project/fruits/pear.js': [ '/**', ' * @providesModule Pear', ' */', 'const Banana = require("Banana");', 'const Strawberry = require(`Strawberry`);', ].join('\n'), - '/fruits/strawberry.js': [ + '/project/fruits/strawberry.js': [ '/**', ' * @providesModule Strawberry', ' */', ].join('\n'), - '/package.json': [ + '/project/package.json': [ '{', ' "name": "haste-package",', ' "main": "foo.js"', @@ -73,30 +83,36 @@ describe('worker', () => { it('parses JavaScript files and extracts module information', async () => { expect( - await worker({computeDependencies: true, filePath: '/fruits/pear.js'}), + await worker({ + computeDependencies: true, + filePath: '/project/fruits/pear.js', + rootDir, + }), ).toEqual({ dependencies: ['Banana', 'Strawberry'], id: 'Pear', - module: ['/fruits/pear.js', H.MODULE], + module: ['fruits/pear.js', H.MODULE], }); expect( await worker({ computeDependencies: true, - filePath: '/fruits/strawberry.js', + filePath: '/project/fruits/strawberry.js', + rootDir, }), ).toEqual({ dependencies: [], id: 'Strawberry', - module: ['/fruits/strawberry.js', H.MODULE], + module: ['fruits/strawberry.js', H.MODULE], }); }); it('delegates to hasteImplModulePath for getting the id', async () => { const moduleData = await worker({ computeDependencies: true, - filePath: '/fruits/strawberry.js', + filePath: '/project/fruits/strawberry.js', hasteImplModulePath: path.resolve(__dirname, 'haste_impl.js'), + rootDir, }); expect(moduleData.id).toBe('strawberry'); @@ -111,11 +127,15 @@ describe('worker', () => { it('parses package.json files as haste packages', async () => { expect( - await worker({computeDependencies: true, filePath: '/package.json'}), + await worker({ + computeDependencies: true, + filePath: '/project/package.json', + rootDir, + }), ).toEqual({ dependencies: undefined, id: 'haste-package', - module: ['/package.json', H.PACKAGE], + module: ['package.json', H.PACKAGE], }); }); @@ -123,7 +143,7 @@ describe('worker', () => { let error = null; try { - await worker({computeDependencies: true, filePath: '/kiwi.js'}); + await worker({computeDependencies: true, filePath: '/kiwi.js', rootDir}); } catch (err) { error = err; } @@ -133,23 +153,39 @@ describe('worker', () => { it('simply computes SHA-1s when requested (works well with binary data)', async () => { expect( - await getSha1({computeSha1: true, filePath: '/fruits/apple.png'}), + await getSha1({ + computeSha1: true, + filePath: '/project/fruits/apple.png', + rootDir, + }), ).toEqual({sha1: '4caece539b039b16e16206ea2478f8c5ffb2ca05'}); expect( - await getSha1({computeSha1: false, filePath: '/fruits/banana.js'}), + await getSha1({ + computeSha1: false, + filePath: '/project/fruits/banana.js', + rootDir, + }), ).toEqual({sha1: null}); expect( - await getSha1({computeSha1: true, filePath: '/fruits/banana.js'}), + await getSha1({ + computeSha1: true, + filePath: '/project/fruits/banana.js', + rootDir, + }), ).toEqual({sha1: 'f24c6984cce6f032f6d55d771d04ab8dbbe63c8c'}); expect( - await getSha1({computeSha1: true, filePath: '/fruits/pear.js'}), + await getSha1({ + computeSha1: true, + filePath: '/project/fruits/pear.js', + rootDir, + }), ).toEqual({sha1: '1bf6fc618461c19553e27f8b8021c62b13ff614a'}); await expect( - getSha1({computeSha1: true, filePath: '/i/dont/exist.js'}), + getSha1({computeSha1: true, filePath: '/i/dont/exist.js', rootDir}), ).rejects.toThrow(); }); @@ -157,13 +193,14 @@ describe('worker', () => { expect( await worker({ computeDependencies: false, - filePath: '/fruits/pear.js', + filePath: '/project/fruits/pear.js', hasteImplModulePath: path.resolve(__dirname, 'haste_impl.js'), + rootDir, }), ).toEqual({ dependencies: undefined, id: 'pear', - module: ['/fruits/pear.js', H.MODULE], + module: ['fruits/pear.js', H.MODULE], sha1: undefined, }); diff --git a/packages/jest-haste-map/src/crawlers/__tests__/node.test.js b/packages/jest-haste-map/src/crawlers/__tests__/node.test.js index cd724395ae27..d66ac25efec1 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/node.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/node.test.js @@ -55,9 +55,9 @@ jest.mock('fs', () => { return { lstat: jest.fn(stat), readdir: jest.fn((dir, callback) => { - if (dir === '/fruits') { + if (dir === '/project/fruits') { setTimeout(() => callback(null, ['directory', 'tomato.js']), 0); - } else if (dir === '/fruits/directory') { + } else if (dir === '/project/fruits/directory') { setTimeout(() => callback(null, ['strawberry.js']), 0); } else if (dir == '/error') { setTimeout(() => callback({code: 'ENOTDIR'}, undefined), 0); @@ -70,6 +70,7 @@ jest.mock('fs', () => { const pearMatcher = path => /pear/.test(path); const createMap = obj => new Map(Object.keys(obj).map(key => [key, obj[key]])); +const rootDir = '/project'; let mockResponse; let nodeCrawl; let childProcess; @@ -84,9 +85,9 @@ describe('node crawler', () => { delete process.platform; mockResponse = [ - '/fruits/pear.js', - '/fruits/strawberry.js', - '/fruits/tomato.js', + '/project/fruits/pear.js', + '/project/fruits/strawberry.js', + '/project/fruits/tomato.js', ].join('\n'); }); @@ -97,10 +98,10 @@ describe('node crawler', () => { nodeCrawl = require('../node'); mockResponse = [ - '/fruits/pear.js', - '/fruits/strawberry.js', - '/fruits/tomato.js', - '/vegetables/melon.json', + '/project/fruits/pear.js', + '/project/fruits/strawberry.js', + '/project/fruits/tomato.js', + '/project/vegetables/melon.json', ].join('\n'); const promise = nodeCrawl({ @@ -109,11 +110,12 @@ describe('node crawler', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, - roots: ['/fruits', '/vegtables'], + rootDir, + roots: ['/project/fruits', '/project/vegtables'], }).then(data => { expect(childProcess.spawn).lastCalledWith('find', [ - '/fruits', - '/vegtables', + '/project/fruits', + '/project/vegtables', '-type', 'f', '(', @@ -129,9 +131,9 @@ describe('node crawler', () => { expect(data.files).toEqual( createMap({ - '/fruits/strawberry.js': ['', 32, 0, [], null], - '/fruits/tomato.js': ['', 33, 0, [], null], - '/vegetables/melon.json': ['', 34, 0, [], null], + 'fruits/strawberry.js': ['', 32, 0, [], null], + 'fruits/tomato.js': ['', 33, 0, [], null], + 'vegetables/melon.json': ['', 34, 0, [], null], }), ); }); @@ -147,25 +149,26 @@ describe('node crawler', () => { // In this test sample, strawberry is changed and tomato is unchanged const tomato = ['', 33, 1, [], null]; const files = createMap({ - '/fruits/strawberry.js': ['', 30, 1, [], null], - '/fruits/tomato.js': tomato, + 'fruits/strawberry.js': ['', 30, 1, [], null], + 'fruits/tomato.js': tomato, }); return nodeCrawl({ data: {files}, extensions: ['js'], ignore: pearMatcher, - roots: ['/fruits'], + rootDir, + roots: ['/project/fruits'], }).then(data => { expect(data.files).toEqual( createMap({ - '/fruits/strawberry.js': ['', 32, 0, [], null], - '/fruits/tomato.js': tomato, + 'fruits/strawberry.js': ['', 32, 0, [], null], + 'fruits/tomato.js': tomato, }), ); // Make sure it is the *same* unchanged object. - expect(data.files.get('/fruits/tomato.js')).toBe(tomato); + expect(data.files.get('fruits/tomato.js')).toBe(tomato); }); }); @@ -180,12 +183,13 @@ describe('node crawler', () => { }, extensions: ['js'], ignore: pearMatcher, - roots: ['/fruits'], + rootDir, + roots: ['/project/fruits'], }).then(data => { expect(data.files).toEqual( createMap({ - '/fruits/directory/strawberry.js': ['', 33, 0, [], null], - '/fruits/tomato.js': ['', 32, 0, [], null], + 'fruits/directory/strawberry.js': ['', 33, 0, [], null], + 'fruits/tomato.js': ['', 32, 0, [], null], }), ); }); @@ -202,12 +206,13 @@ describe('node crawler', () => { extensions: ['js'], forceNodeFilesystemAPI: true, ignore: pearMatcher, - roots: ['/fruits'], + rootDir, + roots: ['/project/fruits'], }).then(data => { expect(data.files).toEqual( createMap({ - '/fruits/directory/strawberry.js': ['', 33, 0, [], null], - '/fruits/tomato.js': ['', 32, 0, [], null], + 'fruits/directory/strawberry.js': ['', 33, 0, [], null], + 'fruits/tomato.js': ['', 32, 0, [], null], }), ); }); @@ -224,6 +229,7 @@ describe('node crawler', () => { extensions: ['js'], forceNodeFilesystemAPI: true, ignore: pearMatcher, + rootDir, roots: [], }).then(data => { expect(data.files).toEqual(new Map()); @@ -240,6 +246,7 @@ describe('node crawler', () => { data: {files}, extensions: ['js'], ignore: pearMatcher, + rootDir, roots: ['/error'], }).then(data => { expect(data.files).toEqual(new Map()); diff --git a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js index 071acb2fcd32..0276a8faa577 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -34,14 +34,16 @@ let mockResponse; let mockFiles; const ROOT_MOCK = path.sep === '/' ? '/root-mock' : 'M:\\root-mock'; -const FRUITS = `${ROOT_MOCK}${path.sep}fruits`; -const VEGETABLES = `${ROOT_MOCK}${path.sep}vegetables`; +const FRUITS_RELATIVE = 'fruits'; +const VEGETABLES_RELATIVE = 'vegetables'; +const FRUITS = path.resolve(ROOT_MOCK, FRUITS_RELATIVE); +const VEGETABLES = path.resolve(ROOT_MOCK, VEGETABLES_RELATIVE); const ROOTS = [FRUITS, VEGETABLES]; -const BANANA = path.join(FRUITS, 'banana.js'); -const STRAWBERRY = path.join(FRUITS, 'strawberry.js'); -const KIWI = path.join(FRUITS, 'kiwi.js'); -const TOMATO = path.join(FRUITS, 'tomato.js'); -const MELON = path.join(VEGETABLES, 'melon.json'); +const BANANA_RELATIVE = path.join(FRUITS_RELATIVE, 'banana.js'); +const STRAWBERRY_RELATIVE = path.join(FRUITS_RELATIVE, 'strawberry.js'); +const KIWI_RELATIVE = path.join(FRUITS_RELATIVE, 'kiwi.js'); +const TOMATO_RELATIVE = path.join(FRUITS_RELATIVE, 'tomato.js'); +const MELON_RELATIVE = path.join(VEGETABLES_RELATIVE, 'melon.json'); const WATCH_PROJECT_MOCK = { [FRUITS]: { relative_path: 'fruits', @@ -100,9 +102,9 @@ describe('watchman watch', () => { }; mockFiles = createMap({ - [MELON]: ['', 33, 0, [], null], - [STRAWBERRY]: ['', 30, 0, [], null], - [TOMATO]: ['', 31, 0, [], null], + [MELON_RELATIVE]: ['', 33, 0, [], null], + [STRAWBERRY_RELATIVE]: ['', 30, 0, [], null], + [TOMATO_RELATIVE]: ['', 31, 0, [], null], }); }); @@ -118,6 +120,7 @@ describe('watchman watch', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, + rootDir: ROOT_MOCK, roots: ROOTS, }).then(data => { const client = watchman.Client.mock.instances[0]; @@ -152,7 +155,7 @@ describe('watchman watch', () => { expect(data.clocks).toEqual( createMap({ - [ROOT_MOCK]: 'c:fake-clock:1', + '': 'c:fake-clock:1', }), ); @@ -191,7 +194,7 @@ describe('watchman watch', () => { }; const clocks = createMap({ - [ROOT_MOCK]: 'c:fake-clock:1', + '': 'c:fake-clock:1', }); return watchmanCrawl({ @@ -201,6 +204,7 @@ describe('watchman watch', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, + rootDir: ROOT_MOCK, roots: ROOTS, }).then(data => { // The object was reused. @@ -208,15 +212,15 @@ describe('watchman watch', () => { expect(data.clocks).toEqual( createMap({ - [ROOT_MOCK]: 'c:fake-clock:2', + '': 'c:fake-clock:2', }), ); expect(data.files).toEqual( createMap({ - [KIWI]: ['', 42, 0, [], null], - [MELON]: ['', 33, 0, [], null], - [STRAWBERRY]: ['', 30, 0, [], null], + [KIWI_RELATIVE]: ['', 42, 0, [], null], + [MELON_RELATIVE]: ['', 33, 0, [], null], + [STRAWBERRY_RELATIVE]: ['', 30, 0, [], null], }), ); }); @@ -260,12 +264,12 @@ describe('watchman watch', () => { }; const mockBananaMetadata = ['Banana', 41, 1, ['Raspberry'], null]; - mockFiles.set(BANANA, mockBananaMetadata); + mockFiles.set(BANANA_RELATIVE, mockBananaMetadata); const mockTomatoMetadata = ['Tomato', 31, 1, [], mockTomatoSha1]; - mockFiles.set(TOMATO, mockTomatoMetadata); + mockFiles.set(TOMATO_RELATIVE, mockTomatoMetadata); const clocks = createMap({ - [ROOT_MOCK]: 'c:fake-clock:1', + '': 'c:fake-clock:1', }); return watchmanCrawl({ @@ -275,6 +279,7 @@ describe('watchman watch', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, + rootDir: ROOT_MOCK, roots: ROOTS, }).then(data => { // The file object was *not* reused. @@ -282,25 +287,25 @@ describe('watchman watch', () => { expect(data.clocks).toEqual( createMap({ - [ROOT_MOCK]: 'c:fake-clock:3', + '': 'c:fake-clock:3', }), ); // /fruits/strawberry.js was removed from the file list. expect(data.files).toEqual( createMap({ - [BANANA]: mockBananaMetadata, - [KIWI]: ['', 42, 0, [], null], - [TOMATO]: ['Tomato', 76, 1, [], mockTomatoSha1], + [BANANA_RELATIVE]: mockBananaMetadata, + [KIWI_RELATIVE]: ['', 42, 0, [], null], + [TOMATO_RELATIVE]: ['Tomato', 76, 1, [], mockTomatoSha1], }), ); // Even though the file list was reset, old file objects are still reused // if no changes have been made - expect(data.files.get(BANANA)).toBe(mockBananaMetadata); + expect(data.files.get(BANANA_RELATIVE)).toBe(mockBananaMetadata); // Old file objects are not reused if they have a different mtime - expect(data.files.get(TOMATO)).not.toBe(mockTomatoMetadata); + expect(data.files.get(TOMATO_RELATIVE)).not.toBe(mockTomatoMetadata); }); }); @@ -348,8 +353,8 @@ describe('watchman watch', () => { }; const clocks = createMap({ - [FRUITS]: 'c:fake-clock:1', - [VEGETABLES]: 'c:fake-clock:2', + [FRUITS_RELATIVE]: 'c:fake-clock:1', + [VEGETABLES_RELATIVE]: 'c:fake-clock:2', }); return watchmanCrawl({ @@ -359,19 +364,20 @@ describe('watchman watch', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, + rootDir: ROOT_MOCK, roots: ROOTS, }).then(data => { expect(data.clocks).toEqual( createMap({ - [FRUITS]: 'c:fake-clock:3', - [VEGETABLES]: 'c:fake-clock:4', + [FRUITS_RELATIVE]: 'c:fake-clock:3', + [VEGETABLES_RELATIVE]: 'c:fake-clock:4', }), ); expect(data.files).toEqual( createMap({ - [KIWI]: ['', 42, 0, [], null], - [MELON]: ['', 33, 0, [], null], + [KIWI_RELATIVE]: ['', 42, 0, [], null], + [MELON_RELATIVE]: ['', 33, 0, [], null], }), ); }); @@ -414,6 +420,7 @@ describe('watchman watch', () => { }, extensions: ['js', 'json'], ignore: pearMatcher, + rootDir: ROOT_MOCK, roots: [...ROOTS, ROOT_MOCK], }).then(data => { const client = watchman.Client.mock.instances[0]; @@ -443,7 +450,7 @@ describe('watchman watch', () => { expect(data.clocks).toEqual( createMap({ - [ROOT_MOCK]: 'c:fake-clock:1', + '': 'c:fake-clock:1', }), ); @@ -482,6 +489,7 @@ describe('watchman watch', () => { files: new Map(), }, extensions: ['js', 'json'], + rootDir: ROOT_MOCK, roots: [ROOT_MOCK], }); @@ -521,6 +529,7 @@ describe('watchman watch', () => { files: new Map(), }, extensions: ['js', 'json'], + rootDir: ROOT_MOCK, roots: [ROOT_MOCK], }); diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index 16e1e89114b7..7f069afa51a1 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -127,20 +127,28 @@ function findNative( module.exports = function nodeCrawl( options: CrawlerOptions, ): Promise { - const {data, extensions, forceNodeFilesystemAPI, ignore, roots} = options; + const { + data, + extensions, + forceNodeFilesystemAPI, + ignore, + rootDir, + roots, + } = options; return new Promise(resolve => { const callback = list => { const files = new Map(); list.forEach(fileData => { - const name = fileData[0]; + const filePath = fileData[0]; + const relativeFilePath = path.relative(rootDir, filePath); const mtime = fileData[1]; - const existingFile = data.files.get(name); + const existingFile = data.files.get(relativeFilePath); if (existingFile && existingFile[H.MTIME] === mtime) { - files.set(name, existingFile); + files.set(relativeFilePath, existingFile); } else { // See ../constants.js; SHA-1 will always be null and fulfilled later. - files.set(name, ['', mtime, 0, [], null]); + files.set(relativeFilePath, ['', mtime, 0, [], null]); } }); data.files = files; diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index cc7a9dfa6f47..17515c9f40cd 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -29,7 +29,7 @@ module.exports = async function watchmanCrawl( options: CrawlerOptions, ): Promise { const fields = ['name', 'exists', 'mtime_ms']; - const {data, extensions, ignore, roots} = options; + const {data, extensions, ignore, rootDir, roots} = options; const defaultWatchExpression = [ 'allof', ['type', 'f'], @@ -112,9 +112,10 @@ module.exports = async function watchmanCrawl( } } - const query = clocks.has(root) + const relativeRoot = path.relative(rootDir, root); + const query = clocks.has(relativeRoot) ? // Use the `since` generator if we have a clock available - {expression, fields, since: clocks.get(root)} + {expression, fields, since: clocks.get(relativeRoot)} : // Otherwise use the `glob` filter {expression, fields, glob}; @@ -159,12 +160,15 @@ module.exports = async function watchmanCrawl( for (const [watchRoot, response] of watchmanFiles) { const fsRoot = normalizePathSep(watchRoot); - clocks.set(fsRoot, response.clock); + const relativeFsRoot = path.relative(rootDir, fsRoot); + clocks.set(relativeFsRoot, response.clock); for (const fileData of response.files) { - const name = fsRoot + path.sep + normalizePathSep(fileData.name); + const filePath = fsRoot + path.sep + normalizePathSep(fileData.name); + const relativeFilePath = path.relative(rootDir, filePath); + if (!fileData.exists) { - files.delete(name); - } else if (!ignore(name)) { + files.delete(relativeFilePath); + } else if (!ignore(filePath)) { const mtime = typeof fileData.mtime_ms === 'number' ? fileData.mtime_ms @@ -174,15 +178,15 @@ module.exports = async function watchmanCrawl( sha1hex = null; } - const existingFileData = data.files.get(name); + const existingFileData = data.files.get(relativeFilePath); if (existingFileData && existingFileData[H.MTIME] === mtime) { - files.set(name, existingFileData); + files.set(relativeFilePath, existingFileData); } else if ( existingFileData && sha1hex && existingFileData[H.SHA1] === sha1hex ) { - files.set(name, [ + files.set(relativeFilePath, [ existingFileData[0], mtime, existingFileData[2], @@ -191,7 +195,7 @@ module.exports = async function watchmanCrawl( ]); } else { // See ../constants.js - files.set(name, ['', mtime, 0, [], sha1hex]); + files.set(relativeFilePath, ['', mtime, 0, [], sha1hex]); } } } diff --git a/packages/jest-haste-map/src/haste_fs.js b/packages/jest-haste-map/src/haste_fs.js index af38d68ea700..3aef88898c1f 100644 --- a/packages/jest-haste-map/src/haste_fs.js +++ b/packages/jest-haste-map/src/haste_fs.js @@ -15,33 +15,41 @@ import micromatch from 'micromatch'; import H from './constants'; export default class HasteFS { + _rootDir: Path; _files: FileData; - constructor(files: FileData) { + constructor({rootDir, files}: {rootDir: Path, files: FileData}) { + this._rootDir = rootDir; this._files = files; } getModuleName(file: Path): ?string { - const fileMetadata = this._files.get(file); + const fileMetadata = this._getFileData(file); return (fileMetadata && fileMetadata[H.ID]) || null; } getDependencies(file: Path): ?Array { - const fileMetadata = this._files.get(file); + const fileMetadata = this._getFileData(file); return (fileMetadata && fileMetadata[H.DEPENDENCIES]) || null; } getSha1(file: Path): ?string { - const fileMetadata = this._files.get(file); + const fileMetadata = this._getFileData(file); return (fileMetadata && fileMetadata[H.SHA1]) || null; } exists(file: Path): boolean { - return this._files.has(file); + return this._getFileData(file) != null; } getAllFiles(): Array { - return Array.from(this._files.keys()); + return Array.from(this.getFileIterator()); + } + + *getFileIterator(): Iterator { + for (const file of this._files.keys()) { + yield path.resolve(this._rootDir, file); + } } getFileIterator(): Iterator { @@ -53,7 +61,7 @@ export default class HasteFS { pattern = new RegExp(pattern); } const files = []; - for (const file of this._files.keys()) { + for (const file of this.getFileIterator()) { if (pattern.test(file)) { files.push(file); } @@ -63,7 +71,7 @@ export default class HasteFS { matchFilesWithGlob(globs: Array, root: ?Path): Set { const files = new Set(); - for (const file of this._files.keys()) { + for (const file of this.getFileIterator()) { const filePath = root ? path.relative(root, file) : file; if (micromatch([filePath], globs).length) { files.add(file); @@ -71,4 +79,9 @@ export default class HasteFS { } return files; } + + _getFileData(file: Path) { + const relativePath = path.relative(this._rootDir, file); + return this._files.get(relativePath); + } } diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index bda8666e1ab3..fb62e26c8f7d 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -61,6 +61,7 @@ type Options = { providesModuleNodeModules?: Array, resetCache?: boolean, retainAllFiles: boolean, + rootDir: string, roots: Array, throwOnModuleCollision?: boolean, useWatchman?: boolean, @@ -81,6 +82,7 @@ type InternalOptions = { platforms: Array, resetCache: ?boolean, retainAllFiles: boolean, + rootDir: string, roots: Array, throwOnModuleCollision: boolean, useWatchman: boolean, @@ -239,6 +241,7 @@ class HasteMap extends EventEmitter { platforms: options.platforms, resetCache: options.resetCache, retainAllFiles: options.retainAllFiles, + rootDir: options.rootDir, roots: Array.from(new Set(options.roots)), throwOnModuleCollision: !!options.throwOnModuleCollision, useWatchman: options.useWatchman == null ? true : options.useWatchman, @@ -286,11 +289,17 @@ class HasteMap extends EventEmitter { .then(data => this._buildHasteMap(data)) .then(hasteMap => { this._persist(hasteMap); - const hasteFS = new HasteFS(hasteMap.files); + + const rootDir = this._options.rootDir; + const hasteFS = new HasteFS({ + files: hasteMap.files, + rootDir, + }); const moduleMap = new HasteModuleMap({ duplicates: hasteMap.duplicates, map: hasteMap.map, mocks: hasteMap.mocks, + rootDir, }); const __hasteMapForTest = (process.env.NODE_ENV === 'test' && hasteMap) || null; @@ -325,6 +334,7 @@ class HasteMap extends EventEmitter { duplicates: data.duplicates, map: data.map, mocks: data.mocks, + rootDir: this._options.rootDir, }); } @@ -342,9 +352,9 @@ class HasteMap extends EventEmitter { .catch(() => this._createEmptyMap()) .then(cachedHasteMap => { const cachedFiles = []; - for (const [filePath, fileMetadata] of cachedHasteMap.files) { + for (const [relativeFilePath, fileMetadata] of cachedHasteMap.files) { const moduleName = fileMetadata[H.ID]; - cachedFiles.push({moduleName, path: filePath}); + cachedFiles.push({moduleName, path: relativeFilePath}); } return this._crawl(cachedHasteMap).then(hasteMap => { const deprecatedFiles = cachedFiles.filter(file => { @@ -366,6 +376,8 @@ class HasteMap extends EventEmitter { filePath: Path, workerOptions: ?{forceInBand: boolean}, ): ?Promise { + const rootDir = this._options.rootDir; + const setModule = (id: string, module: ModuleMetaData) => { let moduleMap = map.get(id); if (!moduleMap) { @@ -381,8 +393,8 @@ class HasteMap extends EventEmitter { const message = `jest-haste-map: @providesModule naming collision:\n` + ` Duplicate module name: ${id}\n` + - ` Paths: ${module[H.PATH]} collides with ` + - `${existingModule[H.PATH]}\n\nThis ` + + ` Paths: ${path.resolve(rootDir, module[H.PATH])} collides with ` + + `${path.resolve(rootDir, existingModule[H.PATH])}\n\nThis ` + `${this._options.throwOnModuleCollision ? 'error' : 'warning'} ` + `is caused by a @providesModule declaration ` + `with the same name across two different files.`; @@ -418,7 +430,8 @@ class HasteMap extends EventEmitter { moduleMap[platform] = module; }; - const fileMetadata = hasteMap.files.get(filePath); + const relativeFilePath = path.relative(rootDir, filePath); + const fileMetadata = hasteMap.files.get(relativeFilePath); if (!fileMetadata) { throw new Error( 'jest-haste-map: File to process was not found in the haste map.', @@ -462,7 +475,7 @@ class HasteMap extends EventEmitter { // If a file cannot be read we remove it from the file list and // ignore the failure silently. - hasteMap.files.delete(filePath); + hasteMap.files.delete(relativeFilePath); }; // If we retain all files in the virtual HasteFS representation, we avoid @@ -475,6 +488,7 @@ class HasteMap extends EventEmitter { computeSha1, filePath, hasteImplModulePath: this._options.hasteImplModulePath, + rootDir, }) .then(workerReply, workerError); } @@ -497,10 +511,10 @@ class HasteMap extends EventEmitter { `Jest will use the mock file found in: \n` + `${filePath}\n` + ` Please delete one of the following two files: \n ` + - `${existingMockPath}\n${filePath}\n\n`, + `${path.join(rootDir, existingMockPath)}\n${filePath}\n\n`, ); } - mocks.set(mockPath, filePath); + mocks.set(mockPath, relativeFilePath); } if (fileMetadata[H.VISITED]) { @@ -537,6 +551,7 @@ class HasteMap extends EventEmitter { computeSha1, filePath, hasteImplModulePath: this._options.hasteImplModulePath, + rootDir, }) .then(workerReply, workerError); } @@ -555,8 +570,9 @@ class HasteMap extends EventEmitter { this._recoverDuplicates(hasteMap, file.path, file.moduleName); } - for (const filePath of hasteMap.files.keys()) { + for (const relativeFilePath of hasteMap.files.keys()) { // SHA-1, if requested, should already be present thanks to the crawler. + const filePath = path.resolve(this._options.rootDir, relativeFilePath); const promise = this._processFile(hasteMap, map, mocks, filePath); if (promise) { promises.push(promise); @@ -637,6 +653,7 @@ class HasteMap extends EventEmitter { extensions: options.extensions, forceNodeFilesystemAPI: options.forceNodeFilesystemAPI, ignore, + rootDir: options.rootDir, roots: options.roots, }).catch(e => { throw new Error( @@ -657,6 +674,7 @@ class HasteMap extends EventEmitter { extensions: options.extensions, forceNodeFilesystemAPI: options.forceNodeFilesystemAPI, ignore, + rootDir: options.rootDir, roots: options.roots, }).catch(retry); } catch (error) { @@ -689,6 +707,8 @@ class HasteMap extends EventEmitter { : sane.NodeWatcher; const extensions = this._options.extensions; const ignorePattern = this._options.ignorePattern; + const rootDir = this._options.rootDir; + let changeQueue = Promise.resolve(); let eventsQueue = []; // We only need to copy the entire haste map once on every "frame". @@ -720,11 +740,15 @@ class HasteMap extends EventEmitter { mustCopy = true; this.emit('change', { eventsQueue, - hasteFS: new HasteFS(hasteMap.files), + hasteFS: new HasteFS({ + files: hasteMap.files, + rootDir, + }), moduleMap: new HasteModuleMap({ duplicates: hasteMap.duplicates, map: hasteMap.map, mocks: hasteMap.mocks, + rootDir, }), }); eventsQueue = []; @@ -776,7 +800,8 @@ class HasteMap extends EventEmitter { const add = () => eventsQueue.push({filePath, stat, type}); - const fileMetadata = hasteMap.files.get(filePath); + const relativeFilePath = path.relative(rootDir, filePath); + const fileMetadata = hasteMap.files.get(relativeFilePath); // If it's not an addition, delete the file and all its metadata if (fileMetadata != null) { @@ -784,7 +809,7 @@ class HasteMap extends EventEmitter { const platform = getPlatformExtension(filePath, this._options.platforms) || H.GENERIC_PLATFORM; - hasteMap.files.delete(filePath); + hasteMap.files.delete(relativeFilePath); let moduleMap = hasteMap.map.get(moduleName); if (moduleMap != null) { @@ -807,7 +832,7 @@ class HasteMap extends EventEmitter { hasteMap.mocks.delete(mockName); } - this._recoverDuplicates(hasteMap, filePath, moduleName); + this._recoverDuplicates(hasteMap, relativeFilePath, moduleName); } // If the file was added or changed, @@ -818,7 +843,7 @@ class HasteMap extends EventEmitter { 'since the file exists or changed, it should have stats', ); const fileMetadata = ['', stat.mtime.getTime(), 0, [], null]; - hasteMap.files.set(filePath, fileMetadata); + hasteMap.files.set(relativeFilePath, fileMetadata); const promise = this._processFile( hasteMap, hasteMap.map, @@ -865,7 +890,7 @@ class HasteMap extends EventEmitter { */ _recoverDuplicates( hasteMap: InternalHasteMap, - filePath: string, + relativeFilePath: string, moduleName: string, ) { let dupsByPlatform = hasteMap.duplicates.get(moduleName); @@ -874,7 +899,7 @@ class HasteMap extends EventEmitter { } const platform = - getPlatformExtension(filePath, this._options.platforms) || + getPlatformExtension(relativeFilePath, this._options.platforms) || H.GENERIC_PLATFORM; let dups = dupsByPlatform[platform]; if (dups == null) { @@ -886,8 +911,8 @@ class HasteMap extends EventEmitter { dups = copy(dups); dupsByPlatform[platform] = dups; - const dedupType = dups[filePath]; - delete dups[filePath]; + const dedupType = dups[relativeFilePath]; + delete dups[relativeFilePath]; const filePaths = Object.keys(dups); if (filePaths.length > 1) { return; diff --git a/packages/jest-haste-map/src/module_map.js b/packages/jest-haste-map/src/module_map.js index fdf673d01cd0..6e15fd6b3373 100644 --- a/packages/jest-haste-map/src/module_map.js +++ b/packages/jest-haste-map/src/module_map.js @@ -18,6 +18,7 @@ import type { MockData, } from 'types/HasteMap'; +import path from 'path'; import H from './constants'; const EMPTY_MAP = {}; @@ -30,6 +31,7 @@ export opaque type SerializableModuleMap = { >, map: $Call>>, mocks: $Call>>, + rootDir: string, }; export default class ModuleMap { @@ -55,7 +57,8 @@ export default class ModuleMap { !!supportsNativePlatform, ); if (module && module[H.TYPE] === type) { - return module[H.PATH]; + const modulePath = module[H.PATH]; + return modulePath && path.resolve(this._raw.rootDir, modulePath); } return null; } @@ -69,7 +72,9 @@ export default class ModuleMap { } getMockModule(name: string): ?Path { - return this._raw.mocks.get(name) || this._raw.mocks.get(name + '/index'); + const mockPath = + this._raw.mocks.get(name) || this._raw.mocks.get(name + '/index'); + return mockPath && path.resolve(this._raw.rootDir, mockPath); } getRawModuleMap(): RawModuleMap { @@ -77,6 +82,7 @@ export default class ModuleMap { duplicates: this._raw.duplicates, map: this._raw.map, mocks: this._raw.mocks, + rootDir: this._raw.rootDir, }; } @@ -85,6 +91,7 @@ export default class ModuleMap { duplicates: Array.from(this._raw.duplicates), map: Array.from(this._raw.map), mocks: Array.from(this._raw.mocks), + rootDir: this._raw.rootDir, }; } @@ -93,6 +100,7 @@ export default class ModuleMap { duplicates: new Map(serializableModuleMap.duplicates), map: new Map(serializableModuleMap.map), mocks: new Map(serializableModuleMap.mocks), + rootDir: serializableModuleMap.rootDir, }); } @@ -149,11 +157,18 @@ export default class ModuleMap { name: string, platform: string, supportsNativePlatform: boolean, - set: ?DuplicatesSet, + relativePathSet: ?DuplicatesSet, ) { - if (set == null) { + if (relativePathSet == null) { return; } + // Force flow refinement + const previousSet: DuplicatesSet = relativePathSet; + const set = Object.keys(previousSet).reduce((set, relativePath) => { + const duplicatePath = path.resolve(this._raw.rootDir, relativePath); + set[duplicatePath] = previousSet[relativePath]; + return set; + }, Object.create(null)); throw new DuplicateHasteCandidatesError( name, platform, @@ -162,11 +177,12 @@ export default class ModuleMap { ); } - static create() { + static create(rootDir: Path) { return new ModuleMap({ duplicates: new Map(), map: new Map(), mocks: new Map(), + rootDir, }); } } diff --git a/packages/jest-haste-map/src/types.js b/packages/jest-haste-map/src/types.js index 18db89daf8ba..94422a08d920 100644 --- a/packages/jest-haste-map/src/types.js +++ b/packages/jest-haste-map/src/types.js @@ -14,6 +14,7 @@ export type IgnoreMatcher = (item: string) => boolean; export type WorkerMessage = { computeDependencies: boolean, computeSha1: boolean, + rootDir: string, filePath: string, hasteImplModulePath?: string, }; @@ -31,6 +32,7 @@ export type CrawlerOptions = {| extensions: Array, forceNodeFilesystemAPI: boolean, ignore: IgnoreMatcher, + rootDir: string, roots: Array, |}; diff --git a/packages/jest-haste-map/src/worker.js b/packages/jest-haste-map/src/worker.js index 7a60fc965bcc..019622b1caf9 100644 --- a/packages/jest-haste-map/src/worker.js +++ b/packages/jest-haste-map/src/worker.js @@ -48,7 +48,8 @@ export async function worker(data: WorkerMessage): Promise { let module; let sha1; - const {computeDependencies, computeSha1, filePath} = data; + const {computeDependencies, computeSha1, rootDir, filePath} = data; + const getContent = (): string => { if (content === undefined) { content = fs.readFileSync(filePath, 'utf8'); @@ -63,8 +64,9 @@ export async function worker(data: WorkerMessage): Promise { const fileData = JSON.parse(getContent()); if (fileData.name) { + const relativeFilePath = path.relative(rootDir, filePath); id = fileData.name; - module = [filePath, H.PACKAGE]; + module = [relativeFilePath, H.PACKAGE]; } } catch (err) { throw new Error(`Cannot parse ${filePath} as JSON: ${err.message}`); @@ -83,7 +85,8 @@ export async function worker(data: WorkerMessage): Promise { } if (id) { - module = [filePath, H.MODULE]; + const relativeFilePath = path.relative(rootDir, filePath); + module = [relativeFilePath, H.MODULE]; } } diff --git a/packages/jest-resolve/src/__tests__/resolve.test.js b/packages/jest-resolve/src/__tests__/resolve.test.js index 837f4c4c3c98..7d17eebac935 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.js +++ b/packages/jest-resolve/src/__tests__/resolve.test.js @@ -23,7 +23,7 @@ beforeEach(() => { describe('isCoreModule', () => { it('returns false if `hasCoreModules` is false.', () => { - const moduleMap = ModuleMap.create(); + const moduleMap = ModuleMap.create('/'); const resolver = new Resolver(moduleMap, { hasCoreModules: false, }); @@ -32,14 +32,14 @@ describe('isCoreModule', () => { }); it('returns true if `hasCoreModules` is true and `moduleName` is a core module.', () => { - const moduleMap = ModuleMap.create(); + const moduleMap = ModuleMap.create('/'); const resolver = new Resolver(moduleMap, {}); const isCore = resolver.isCoreModule('assert'); expect(isCore).toEqual(true); }); it('returns false if `hasCoreModules` is true and `moduleName` is not a core module.', () => { - const moduleMap = ModuleMap.create(); + const moduleMap = ModuleMap.create('/'); const resolver = new Resolver(moduleMap, {}); const isCore = resolver.isCoreModule('not-a-core-module'); expect(isCore).toEqual(false); @@ -82,7 +82,7 @@ describe('findNodeModule', () => { describe('resolveModule', () => { let moduleMap; beforeEach(() => { - moduleMap = ModuleMap.create(); + moduleMap = ModuleMap.create('/'); }); it('is possible to resolve node modules', () => { @@ -159,7 +159,7 @@ describe('getMockModule', () => { it('is possible to use custom resolver to resolve deps inside mock modules with moduleNameMapper', () => { userResolver.mockImplementation(() => 'module'); - const moduleMap = ModuleMap.create(); + const moduleMap = ModuleMap.create('/'); const resolver = new Resolver(moduleMap, { moduleNameMapper: [ { @@ -196,7 +196,7 @@ describe('Resolver.getModulePaths() -> nodeModulesPaths()', () => { beforeEach(() => { jest.resetModules(); - moduleMap = ModuleMap.create(); + moduleMap = ModuleMap.create('/'); // Mocking realpath to function the old way, where it just looks at // pathstrings instead of actually trying to access the physical directory. diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index d6612eb42221..810097dc2474 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -240,6 +240,7 @@ class Runtime { providesModuleNodeModules: config.haste.providesModuleNodeModules, resetCache: options && options.resetCache, retainAllFiles: false, + rootDir: config.rootDir, roots: config.roots, useWatchman: options && options.watchman, watch: options && options.watch, diff --git a/types/HasteMap.js b/types/HasteMap.js index f4ad4b42e2a6..01865ad57790 100644 --- a/types/HasteMap.js +++ b/types/HasteMap.js @@ -48,6 +48,7 @@ export type HasteMap = {| |}; export type RawModuleMap = {| + rootDir: Path, duplicates: DuplicatesIndex, map: ModuleMapData, mocks: MockData, From a455f1ca5154e79254b79ca6734b8afc219bee3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Fri, 21 Sep 2018 20:41:05 +0100 Subject: [PATCH 2/6] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f72957d6254..4213d68c8110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `[jest-each]` Add each array validation check ([#7033](https://github.com/facebook/jest/pull/7033)) - `[jest-haste-map]` [**BREAKING**] Replace internal data structures to improve performance ([#6960](https://github.com/facebook/jest/pull/6960)) +- `[jest-haste-map]` Used relative paths internally to allow remote caching ([#7020](https://github.com/facebook/jest/pull/7020)) - `[jest-haste-map]` Do not visit again files with the same sha-1 ([#6990](https://github.com/facebook/jest/pull/6990)) - `[jest-jasmine2]` Fix memory leak in Error objects hold by the framework ([#6965](https://github.com/facebook/jest/pull/6965)) - `[jest-haste-map]` Fixed Haste whitelist generation for scoped modules on Windows ([#6980](https://github.com/facebook/jest/pull/6980)) From a10745ba203d32c014954a3976794cdfc9f46780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 24 Sep 2018 12:39:18 +0100 Subject: [PATCH 3/6] Use relative roots for the name of the cached haste map --- CHANGELOG.md | 2 +- packages/jest-haste-map/src/index.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4213d68c8110..01a25cf10b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - `[jest-each]` Add each array validation check ([#7033](https://github.com/facebook/jest/pull/7033)) - `[jest-haste-map]` [**BREAKING**] Replace internal data structures to improve performance ([#6960](https://github.com/facebook/jest/pull/6960)) -- `[jest-haste-map]` Used relative paths internally to allow remote caching ([#7020](https://github.com/facebook/jest/pull/7020)) +- `[jest-haste-map]` Use relative paths to allow remote caching ([#7020](https://github.com/facebook/jest/pull/7020)) - `[jest-haste-map]` Do not visit again files with the same sha-1 ([#6990](https://github.com/facebook/jest/pull/6990)) - `[jest-jasmine2]` Fix memory leak in Error objects hold by the framework ([#6965](https://github.com/facebook/jest/pull/6965)) - `[jest-haste-map]` Fixed Haste whitelist generation for scoped modules on Windows ([#6980](https://github.com/facebook/jest/pull/6980)) diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index fb62e26c8f7d..f045a2a47388 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -258,7 +258,9 @@ class HasteMap extends EventEmitter { this._options.cacheDirectory, `haste-map-${this._options.name}`, VERSION, - this._options.roots.join(':'), + this._options.roots + .map(root => path.relative(options.rootDir, root)) + .join(':'), this._options.extensions.join(':'), this._options.platforms.join(':'), this._options.computeSha1.toString(), From 2536b042de74d103984934a68228af670ad3362c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 24 Sep 2018 16:46:05 +0100 Subject: [PATCH 4/6] Remove duplicated method --- packages/jest-haste-map/src/haste_fs.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/jest-haste-map/src/haste_fs.js b/packages/jest-haste-map/src/haste_fs.js index 3aef88898c1f..501fe2d66165 100644 --- a/packages/jest-haste-map/src/haste_fs.js +++ b/packages/jest-haste-map/src/haste_fs.js @@ -52,10 +52,6 @@ export default class HasteFS { } } - getFileIterator(): Iterator { - return this._files.keys(); - } - matchFiles(pattern: RegExp | string): Array { if (!(pattern instanceof RegExp)) { pattern = new RegExp(pattern); From 9a2222574e4a179b97d794615c4283274e466c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Oct 2018 14:39:02 +0100 Subject: [PATCH 5/6] Improved performance of path manipulations --- packages/jest-haste-map/src/crawlers/node.js | 3 +- .../jest-haste-map/src/crawlers/watchman.js | 8 ++-- packages/jest-haste-map/src/haste_fs.js | 8 ++-- packages/jest-haste-map/src/index.js | 19 +++++--- .../src/lib/__tests__/fast_path.test.js | 45 +++++++++++++++++++ packages/jest-haste-map/src/lib/fast_path.js | 27 +++++++++++ packages/jest-haste-map/src/module_map.js | 8 ++-- 7 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 packages/jest-haste-map/src/lib/__tests__/fast_path.test.js create mode 100644 packages/jest-haste-map/src/lib/fast_path.js diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index 7f069afa51a1..8d8557457ec7 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -14,6 +14,7 @@ import fs from 'fs'; import path from 'path'; import {spawn} from 'child_process'; import H from '../constants'; +import * as fastPath from '../lib/fast_path'; type Callback = (result: Array<[/* id */ string, /* mtime */ number]>) => void; @@ -141,7 +142,7 @@ module.exports = function nodeCrawl( const files = new Map(); list.forEach(fileData => { const filePath = fileData[0]; - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const mtime = fileData[1]; const existingFile = data.files.get(relativeFilePath); if (existingFile && existingFile[H.MTIME] === mtime) { diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 17515c9f40cd..027017afed3e 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -10,6 +10,7 @@ import type {InternalHasteMap} from 'types/HasteMap'; import type {CrawlerOptions} from '../types'; +import * as fastPath from '../lib/fast_path'; import normalizePathSep from '../lib/normalize_path_sep'; import path from 'path'; import watchman from 'fb-watchman'; @@ -112,7 +113,7 @@ module.exports = async function watchmanCrawl( } } - const relativeRoot = path.relative(rootDir, root); + const relativeRoot = fastPath.relative(rootDir, root); const query = clocks.has(relativeRoot) ? // Use the `since` generator if we have a clock available {expression, fields, since: clocks.get(relativeRoot)} @@ -160,11 +161,12 @@ module.exports = async function watchmanCrawl( for (const [watchRoot, response] of watchmanFiles) { const fsRoot = normalizePathSep(watchRoot); - const relativeFsRoot = path.relative(rootDir, fsRoot); + const relativeFsRoot = fastPath.relative(rootDir, fsRoot); clocks.set(relativeFsRoot, response.clock); + for (const fileData of response.files) { const filePath = fsRoot + path.sep + normalizePathSep(fileData.name); - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); if (!fileData.exists) { files.delete(relativeFilePath); diff --git a/packages/jest-haste-map/src/haste_fs.js b/packages/jest-haste-map/src/haste_fs.js index 501fe2d66165..69df848a5975 100644 --- a/packages/jest-haste-map/src/haste_fs.js +++ b/packages/jest-haste-map/src/haste_fs.js @@ -10,7 +10,7 @@ import type {Glob, Path} from 'types/Config'; import type {FileData} from 'types/HasteMap'; -import path from 'path'; +import * as fastPath from './lib/fast_path'; import micromatch from 'micromatch'; import H from './constants'; @@ -48,7 +48,7 @@ export default class HasteFS { *getFileIterator(): Iterator { for (const file of this._files.keys()) { - yield path.resolve(this._rootDir, file); + yield fastPath.resolve(this._rootDir, file); } } @@ -68,7 +68,7 @@ export default class HasteFS { matchFilesWithGlob(globs: Array, root: ?Path): Set { const files = new Set(); for (const file of this.getFileIterator()) { - const filePath = root ? path.relative(root, file) : file; + const filePath = root ? fastPath.relative(root, file) : file; if (micromatch([filePath], globs).length) { files.add(file); } @@ -77,7 +77,7 @@ export default class HasteFS { } _getFileData(file: Path) { - const relativePath = path.relative(this._rootDir, file); + const relativePath = fastPath.relative(this._rootDir, file); return this._files.get(relativePath); } } diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index f045a2a47388..2cfc28a093ad 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -29,6 +29,7 @@ import serializer from 'jest-serializer'; // eslint-disable-next-line import/default import watchmanCrawl from './crawlers/watchman'; import WatchmanWatcher from './lib/watchman_watcher'; +import * as fastPath from './lib/fast_path'; import Worker from 'jest-worker'; import type {Console} from 'console'; @@ -259,7 +260,7 @@ class HasteMap extends EventEmitter { `haste-map-${this._options.name}`, VERSION, this._options.roots - .map(root => path.relative(options.rootDir, root)) + .map(root => fastPath.relative(options.rootDir, root)) .join(':'), this._options.extensions.join(':'), this._options.platforms.join(':'), @@ -395,8 +396,11 @@ class HasteMap extends EventEmitter { const message = `jest-haste-map: @providesModule naming collision:\n` + ` Duplicate module name: ${id}\n` + - ` Paths: ${path.resolve(rootDir, module[H.PATH])} collides with ` + - `${path.resolve(rootDir, existingModule[H.PATH])}\n\nThis ` + + ` Paths: ${fastPath.resolve( + rootDir, + module[H.PATH], + )} collides with ` + + `${fastPath.resolve(rootDir, existingModule[H.PATH])}\n\nThis ` + `${this._options.throwOnModuleCollision ? 'error' : 'warning'} ` + `is caused by a @providesModule declaration ` + `with the same name across two different files.`; @@ -432,7 +436,7 @@ class HasteMap extends EventEmitter { moduleMap[platform] = module; }; - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const fileMetadata = hasteMap.files.get(relativeFilePath); if (!fileMetadata) { throw new Error( @@ -574,7 +578,10 @@ class HasteMap extends EventEmitter { for (const relativeFilePath of hasteMap.files.keys()) { // SHA-1, if requested, should already be present thanks to the crawler. - const filePath = path.resolve(this._options.rootDir, relativeFilePath); + const filePath = fastPath.resolve( + this._options.rootDir, + relativeFilePath, + ); const promise = this._processFile(hasteMap, map, mocks, filePath); if (promise) { promises.push(promise); @@ -802,7 +809,7 @@ class HasteMap extends EventEmitter { const add = () => eventsQueue.push({filePath, stat, type}); - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const fileMetadata = hasteMap.files.get(relativeFilePath); // If it's not an addition, delete the file and all its metadata diff --git a/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js b/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js new file mode 100644 index 000000000000..d6872fec82de --- /dev/null +++ b/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + +import path from 'path'; +import {relative, resolve} from '../fast_path'; + +describe('fastPath.relative', () => { + it('should get relative paths inside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const filename = path.join(__dirname, 'foo', 'bar', 'baz', 'foobar'); + const relativeFilename = path.join('baz', 'foobar'); + expect(relative(root, filename)).toBe(relativeFilename); + }); + + it('should get relative paths outside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const filename = path.join(__dirname, 'foo', 'baz', 'foobar'); + const relativeFilename = path.join('..', 'baz', 'foobar'); + expect(relative(root, filename)).toBe(relativeFilename); + }); +}); + +describe('fastPath.resolve', () => { + it('should get the absolute path for paths inside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const relativeFilename = path.join('baz', 'foobar'); + const filename = path.join(__dirname, 'foo', 'bar', 'baz', 'foobar'); + expect(resolve(root, relativeFilename)).toBe(filename); + }); + + it('should get the absolute path for paths outside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const relativeFilename = path.join('..', 'baz', 'foobar'); + const filename = path.join(__dirname, 'foo', 'baz', 'foobar'); + expect(resolve(root, relativeFilename)).toBe(filename); + }); +}); diff --git a/packages/jest-haste-map/src/lib/fast_path.js b/packages/jest-haste-map/src/lib/fast_path.js new file mode 100644 index 000000000000..f161ae48bf9e --- /dev/null +++ b/packages/jest-haste-map/src/lib/fast_path.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import path from 'path'; + +// rootDir and filename must be absolute paths (resolved) +export function relative(rootDir: string, filename: string): string { + return filename.indexOf(rootDir) === 0 + ? filename.substr(rootDir.length + 1) + : path.relative(rootDir, filename); +} + +const INDIRECTION_FRAGMENT = '..' + path.sep; + +// rootDir must be an absolute path and relativeFilename must be simple +// (e.g.: foo/bar or ../foo/bar, but never ./foo or foo/../bar) +export function resolve(rootDir: string, relativeFilename: string): string { + return relativeFilename.indexOf(INDIRECTION_FRAGMENT) === 0 + ? path.resolve(rootDir, relativeFilename) + : rootDir + path.sep + relativeFilename; +} diff --git a/packages/jest-haste-map/src/module_map.js b/packages/jest-haste-map/src/module_map.js index 6e15fd6b3373..a305d04c720a 100644 --- a/packages/jest-haste-map/src/module_map.js +++ b/packages/jest-haste-map/src/module_map.js @@ -18,7 +18,7 @@ import type { MockData, } from 'types/HasteMap'; -import path from 'path'; +import * as fastPath from './lib/fast_path'; import H from './constants'; const EMPTY_MAP = {}; @@ -58,7 +58,7 @@ export default class ModuleMap { ); if (module && module[H.TYPE] === type) { const modulePath = module[H.PATH]; - return modulePath && path.resolve(this._raw.rootDir, modulePath); + return modulePath && fastPath.resolve(this._raw.rootDir, modulePath); } return null; } @@ -74,7 +74,7 @@ export default class ModuleMap { getMockModule(name: string): ?Path { const mockPath = this._raw.mocks.get(name) || this._raw.mocks.get(name + '/index'); - return mockPath && path.resolve(this._raw.rootDir, mockPath); + return mockPath && fastPath.resolve(this._raw.rootDir, mockPath); } getRawModuleMap(): RawModuleMap { @@ -165,7 +165,7 @@ export default class ModuleMap { // Force flow refinement const previousSet: DuplicatesSet = relativePathSet; const set = Object.keys(previousSet).reduce((set, relativePath) => { - const duplicatePath = path.resolve(this._raw.rootDir, relativePath); + const duplicatePath = fastPath.resolve(this._raw.rootDir, relativePath); set[duplicatePath] = previousSet[relativePath]; return set; }, Object.create(null)); From 2d847044b5308911cee6226983bd247d50bbd771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Oct 2018 16:15:51 +0100 Subject: [PATCH 6/6] Use rootDir in the haste map cache name --- packages/jest-haste-map/src/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index 2cfc28a093ad..1d2383da5e62 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -255,9 +255,13 @@ class HasteMap extends EventEmitter { 'deprecated. Provide a RegExp instead. See https://github.com/facebook/jest/pull/4063.', ); } + const rootDirHash = crypto + .createHash('md5') + .update(options.rootDir) + .digest('hex'); this._cachePath = HasteMap.getCacheFilePath( this._options.cacheDirectory, - `haste-map-${this._options.name}`, + `haste-map-${this._options.name}-${rootDirHash}`, VERSION, this._options.roots .map(root => fastPath.relative(options.rootDir, root))