From bd982bb7b40ce55ac0cd04266d7018dbbba62c08 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Wed, 9 Jan 2019 12:38:46 +0100 Subject: [PATCH] HasteFS file size (#7580) --- CHANGELOG.md | 3 +- e2e/__tests__/haste_map_size.test.js | 60 ++++++++++++++++++ packages/jest-cli/src/TestSequencer.js | 4 +- .../src/__tests__/test_sequencer.test.js | 7 ++- packages/jest-haste-map/src/HasteFS.js | 5 ++ .../src/__tests__/index.test.js | 62 +++++++++++++------ packages/jest-haste-map/src/constants.js | 7 ++- .../src/crawlers/__tests__/node.test.js | 22 ++++--- .../src/crawlers/__tests__/watchman.test.js | 44 ++++++++----- packages/jest-haste-map/src/crawlers/node.js | 11 ++-- .../jest-haste-map/src/crawlers/watchman.js | 5 +- packages/jest-haste-map/src/index.js | 14 ++++- types/HasteMap.js | 8 ++- 13 files changed, 189 insertions(+), 63 deletions(-) create mode 100644 e2e/__tests__/haste_map_size.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 36eb4dbbea3b..5cb720fb984c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ - `[jest-haste-map]` [**BREAKING**] Remove name from hash in `HasteMap.getCacheFilePath` ([#7218](https://github.com/facebook/jest/pull/7218)) - `[babel-preset-jest]` [**BREAKING**] Export a function instead of an object for Babel 7 compatibility ([#7203](https://github.com/facebook/jest/pull/7203)) - `[jest-haste-map]` [**BREAKING**] Expose relative paths when getting the file iterator ([#7321](https://github.com/facebook/jest/pull/7321)) -- `[jest-cli]` Print version ending in `-dev` when running a local Jest clone +- `[jest-haste-map]` Add `hasteFS.getSize(path)` ([#7580](https://github.com/facebook/jest/pull/7580)) +- `[jest-cli]` Print version ending in `-dev` when running a local Jest clone ([#7582](https://github.com/facebook/jest/pull/7582)) - `[jest-cli]` Add Support for `globalSetup` and `globalTeardown` in projects ([#6865](https://github.com/facebook/jest/pull/6865)) - `[jest-runtime]` Add `extraGlobals` to config to load extra global variables into the execution vm ([#7454](https://github.com/facebook/jest/pull/7454)) - `[jest-util]` Export `specialChars` containing Unicode characters and ANSI escapes for console output ([#7532](https://github.com/facebook/jest/pull/7532)) diff --git a/e2e/__tests__/haste_map_size.test.js b/e2e/__tests__/haste_map_size.test.js new file mode 100644 index 000000000000..3dff41ae1035 --- /dev/null +++ b/e2e/__tests__/haste_map_size.test.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2018-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 + */ + +'use strict'; + +import os from 'os'; +import path from 'path'; +import HasteMap from 'jest-haste-map'; +import {cleanup, writeFiles} from '../Utils'; + +const DIR = path.resolve(os.tmpdir(), 'haste_map_size'); + +beforeEach(() => { + cleanup(DIR); + writeFiles(DIR, { + 'file.js': '"abc"', + }); +}); +afterEach(() => cleanup(DIR)); + +const options = { + extensions: ['js'], + forceNodeFilesystemAPI: true, + ignorePattern: / ^/, + maxWorkers: 2, + mocksPattern: '', + name: 'tmp', + platforms: [], + retainAllFiles: true, + rootDir: DIR, + roots: [DIR], + useWatchman: false, + watch: false, +}; + +test('reports the correct file size', async () => { + const hasteMap = new HasteMap(options); + const hasteFS = (await hasteMap.build()).hasteFS; + expect(hasteFS.getSize(path.join(DIR, 'file.js'))).toBe(5); +}); + +test('updates the file size when a file changes', async () => { + const hasteMap = new HasteMap({...options, watch: true}); + await hasteMap.build(); + + writeFiles(DIR, { + 'file.js': '"asdf"', + }); + const {hasteFS} = await new Promise(resolve => + hasteMap.once('change', resolve), + ); + hasteMap.end(); + expect(hasteFS.getSize(path.join(DIR, 'file.js'))).toBe(6); +}); diff --git a/packages/jest-cli/src/TestSequencer.js b/packages/jest-cli/src/TestSequencer.js index a48d24d2cfcb..fe51829fa58c 100644 --- a/packages/jest-cli/src/TestSequencer.js +++ b/packages/jest-cli/src/TestSequencer.js @@ -69,8 +69,8 @@ export default class TestSequencer { // fastest results. sort(tests: Array): Array { const stats = {}; - const fileSize = test => - stats[test.path] || (stats[test.path] = fs.statSync(test.path).size); + const fileSize = ({path, context: {hasteFS}}) => + stats[path] || (stats[path] = hasteFS.getSize(path) || 0); const hasFailed = (cache, test) => cache[test.path] && cache[test.path][0] === FAIL; const time = (cache, test) => cache[test.path] && cache[test.path][1]; diff --git a/packages/jest-cli/src/__tests__/test_sequencer.test.js b/packages/jest-cli/src/__tests__/test_sequencer.test.js index fc03b111d053..95c8000c3191 100644 --- a/packages/jest-cli/src/__tests__/test_sequencer.test.js +++ b/packages/jest-cli/src/__tests__/test_sequencer.test.js @@ -23,6 +23,9 @@ const context = { cacheDirectory: '/cache', name: 'test', }, + hasteFS: { + getSize: path => path.length, + }, }; const secondContext = { @@ -31,6 +34,9 @@ const secondContext = { cacheDirectory: '/cache2', name: 'test2', }, + hasteFS: { + getSize: path => path.length, + }, }; const toTests = paths => @@ -45,7 +51,6 @@ beforeEach(() => { fs.readFileSync = jest.fn(() => '{}'); fs.existsSync = () => true; - fs.statSync = jest.fn(filePath => ({size: filePath.length})); fs.writeFileSync = jest.fn(); }); diff --git a/packages/jest-haste-map/src/HasteFS.js b/packages/jest-haste-map/src/HasteFS.js index c80432dd47e4..617ef744850e 100644 --- a/packages/jest-haste-map/src/HasteFS.js +++ b/packages/jest-haste-map/src/HasteFS.js @@ -28,6 +28,11 @@ export default class HasteFS { return (fileMetadata && fileMetadata[H.ID]) || null; } + getSize(file: Path): ?number { + const fileMetadata = this._getFileData(file); + return (fileMetadata && fileMetadata[H.SIZE]) || null; + } + getDependencies(file: Path): ?Array { const fileMetadata = this._getFileData(file); return (fileMetadata && fileMetadata[H.DEPENDENCIES]) || null; diff --git a/packages/jest-haste-map/src/__tests__/index.test.js b/packages/jest-haste-map/src/__tests__/index.test.js index d97892877cbd..50a95c8182ec 100644 --- a/packages/jest-haste-map/src/__tests__/index.test.js +++ b/packages/jest-haste-map/src/__tests__/index.test.js @@ -51,7 +51,7 @@ jest.mock('../crawlers/watchman', () => if (list[file]) { const hash = computeSha1 ? mockHashContents(list[file]) : null; - data.files.set(relativeFilePath, ['', 32, 0, [], hash]); + data.files.set(relativeFilePath, ['', 32, 42, 0, [], hash]); } else { data.files.delete(relativeFilePath); } @@ -337,14 +337,15 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - 'fruits/Banana.js': ['Banana', 32, 1, ['Strawberry'], null], - 'fruits/Pear.js': ['Pear', 32, 1, ['Banana', 'Strawberry'], null], - 'fruits/Strawberry.js': ['Strawberry', 32, 1, [], null], - 'fruits/__mocks__/Pear.js': ['', 32, 1, ['Melon'], null], + 'fruits/Banana.js': ['Banana', 32, 42, 1, ['Strawberry'], null], + 'fruits/Pear.js': ['Pear', 32, 42, 1, ['Banana', 'Strawberry'], null], + 'fruits/Strawberry.js': ['Strawberry', 32, 42, 1, [], null], + 'fruits/__mocks__/Pear.js': ['', 32, 42, 1, ['Melon'], null], // node modules 'fruits/node_modules/fbjs/lib/flatMap.js': [ 'flatMap', 32, + 42, 1, [], null, @@ -352,11 +353,12 @@ describe('HasteMap', () => { 'fruits/node_modules/react/React.js': [ 'React', 32, + 42, 1, ['Component'], null, ], - 'vegetables/Melon.js': ['Melon', 32, 1, [], null], + 'vegetables/Melon.js': ['Melon', 32, 42, 1, [], null], }), ); @@ -405,11 +407,18 @@ describe('HasteMap', () => { // The node crawler returns "null" for the SHA-1. data.files = createMap({ - 'fruits/Banana.js': ['Banana', 32, 0, ['Strawberry'], null], - 'fruits/Pear.js': ['Pear', 32, 0, ['Banana', 'Strawberry'], null], - 'fruits/Strawberry.js': ['Strawberry', 32, 0, [], null], - 'fruits/__mocks__/Pear.js': ['', 32, 0, ['Melon'], null], - 'vegetables/Melon.js': ['Melon', 32, 0, [], null], + 'fruits/Banana.js': ['Banana', 32, 42, 0, ['Strawberry'], null], + 'fruits/Pear.js': [ + 'Pear', + 32, + 42, + 0, + ['Banana', 'Strawberry'], + null, + ], + 'fruits/Strawberry.js': ['Strawberry', 32, 42, 0, [], null], + 'fruits/__mocks__/Pear.js': ['', 32, 42, 0, ['Melon'], null], + 'vegetables/Melon.js': ['Melon', 32, 42, 0, [], null], }); return Promise.resolve(data); @@ -430,6 +439,7 @@ describe('HasteMap', () => { 'fruits/Banana.js': [ 'Banana', 32, + 42, 1, ['Strawberry'], '7772b628e422e8cf59c526be4bb9f44c0898e3d1', @@ -437,6 +447,7 @@ describe('HasteMap', () => { 'fruits/Pear.js': [ 'Pear', 32, + 42, 1, ['Banana', 'Strawberry'], '89d0c2cc11dcc5e1df50b8af04ab1b597acfba2f', @@ -444,6 +455,7 @@ describe('HasteMap', () => { 'fruits/Strawberry.js': [ 'Strawberry', 32, + 42, 1, [], 'e8aa38e232b3795f062f1d777731d9240c0f8c25', @@ -451,6 +463,7 @@ describe('HasteMap', () => { 'fruits/__mocks__/Pear.js': [ '', 32, + 42, 1, ['Melon'], '8d40afbb6e2dc78e1ba383b6d02cafad35cceef2', @@ -458,6 +471,7 @@ describe('HasteMap', () => { 'vegetables/Melon.js': [ 'Melon', 32, + 42, 1, [], 'f16ccf6f2334ceff2ddb47628a2c5f2d748198ca', @@ -507,6 +521,7 @@ describe('HasteMap', () => { expect(data.files.get('fruits/node_modules/fbjs/fbjs.js')).toEqual([ '', 32, + 42, 0, [], null, @@ -604,6 +619,7 @@ describe('HasteMap', () => { 'fruits/Strawberry.android.js': [ 'Strawberry', 32, + 42, 1, ['Blackberry'], null, @@ -611,11 +627,12 @@ describe('HasteMap', () => { 'fruits/Strawberry.ios.js': [ 'Strawberry', 32, + 42, 1, ['Raspberry'], null, ], - 'fruits/Strawberry.js': ['Strawberry', 32, 1, ['Banana'], null], + 'fruits/Strawberry.js': ['Strawberry', 32, 42, 1, ['Banana'], null], }), ); @@ -702,7 +719,14 @@ describe('HasteMap', () => { expect(useBuitinsInContext(data.clocks)).toEqual(mockClocks); const files = new Map(initialData.files); - files.set('fruits/Banana.js', ['Banana', 32, 1, ['Kiwi'], null]); + files.set('fruits/Banana.js', [ + 'Banana', + 32, + 42, + 1, + ['Kiwi'], + null, + ]); expect(useBuitinsInContext(data.files)).toEqual(files); @@ -962,7 +986,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, 44, 0, []]); return data; }), ); @@ -1060,7 +1084,7 @@ describe('HasteMap', () => { node.mockImplementation(options => { const {data} = options; data.files = createMap({ - 'fruits/Banana.js': ['', 32, 0, [], null], + 'fruits/Banana.js': ['', 32, 42, 0, [], null], }); return Promise.resolve(data); }); @@ -1073,7 +1097,7 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - 'fruits/Banana.js': ['Banana', 32, 1, ['Strawberry'], null], + 'fruits/Banana.js': ['Banana', 32, 42, 1, ['Strawberry'], null], }), ); @@ -1091,7 +1115,7 @@ describe('HasteMap', () => { node.mockImplementation(options => { const {data} = options; data.files = createMap({ - 'fruits/Banana.js': ['', 32, 0, [], null], + 'fruits/Banana.js': ['', 32, 42, 0, [], null], }); return Promise.resolve(data); }); @@ -1104,7 +1128,7 @@ describe('HasteMap', () => { expect(data.files).toEqual( createMap({ - 'fruits/Banana.js': ['Banana', 32, 1, ['Strawberry'], null], + 'fruits/Banana.js': ['Banana', 32, 42, 1, ['Strawberry'], null], }), ); }); @@ -1184,11 +1208,13 @@ describe('HasteMap', () => { const MOCK_STAT_FILE = { isDirectory: () => false, mtime: {getTime: () => 45}, + size: 55, }; const MOCK_STAT_FOLDER = { isDirectory: () => true, mtime: {getTime: () => 45}, + size: 55, }; hm_it('handles several change events at once', async hm => { diff --git a/packages/jest-haste-map/src/constants.js b/packages/jest-haste-map/src/constants.js index 11ab681ab814..6bbe7c4fb52e 100644 --- a/packages/jest-haste-map/src/constants.js +++ b/packages/jest-haste-map/src/constants.js @@ -20,9 +20,10 @@ export default { /* file map attributes */ ID: 0, MTIME: 1, - VISITED: 2, - DEPENDENCIES: 3, - SHA1: 4, + SIZE: 2, + VISITED: 3, + DEPENDENCIES: 4, + SHA1: 5, /* module map attributes */ PATH: 0, 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 a1e95e66b210..e38f5a003c2f 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/node.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/node.test.js @@ -33,6 +33,7 @@ jest.mock('child_process', () => ({ jest.mock('fs', () => { let mtime = 32; + const size = 42; const stat = (path, callback) => { setTimeout( () => @@ -48,6 +49,7 @@ jest.mock('fs', () => { return mtime++; }, }, + size, }), 0, ); @@ -131,9 +133,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, 42, 0, [], null], + 'fruits/tomato.js': ['', 33, 42, 0, [], null], + 'vegetables/melon.json': ['', 34, 42, 0, [], null], }), ); }); @@ -147,9 +149,9 @@ describe('node crawler', () => { nodeCrawl = require('../node').default; // In this test sample, strawberry is changed and tomato is unchanged - const tomato = ['', 33, 1, [], null]; + const tomato = ['', 33, 42, 1, [], null]; const files = createMap({ - 'fruits/strawberry.js': ['', 30, 1, [], null], + 'fruits/strawberry.js': ['', 30, 40, 1, [], null], 'fruits/tomato.js': tomato, }); @@ -162,7 +164,7 @@ describe('node crawler', () => { }).then(data => { expect(data.files).toEqual( createMap({ - 'fruits/strawberry.js': ['', 32, 0, [], null], + 'fruits/strawberry.js': ['', 32, 42, 0, [], null], 'fruits/tomato.js': tomato, }), ); @@ -188,8 +190,8 @@ describe('node crawler', () => { }).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, 42, 0, [], null], + 'fruits/tomato.js': ['', 32, 42, 0, [], null], }), ); }); @@ -211,8 +213,8 @@ describe('node crawler', () => { }).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, 42, 0, [], null], + 'fruits/tomato.js': ['', 32, 42, 0, [], null], }), ); }); 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 b28a4e3c869f..66816173a4da 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -79,21 +79,25 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 30}, name: 'fruits/strawberry.js', + size: 40, }, { exists: true, mtime_ms: {toNumber: () => 31}, name: 'fruits/tomato.js', + size: 41, }, { exists: true, mtime_ms: {toNumber: () => 32}, name: 'fruits/pear.js', + size: 42, }, { exists: true, mtime_ms: {toNumber: () => 33}, name: 'vegetables/melon.json', + size: 43, }, ], is_fresh_instance: true, @@ -104,9 +108,9 @@ describe('watchman watch', () => { }; mockFiles = createMap({ - [MELON_RELATIVE]: ['', 33, 0, [], null], - [STRAWBERRY_RELATIVE]: ['', 30, 0, [], null], - [TOMATO_RELATIVE]: ['', 31, 0, [], null], + [MELON_RELATIVE]: ['', 33, 43, 0, [], null], + [STRAWBERRY_RELATIVE]: ['', 30, 40, 0, [], null], + [TOMATO_RELATIVE]: ['', 31, 41, 0, [], null], }); }); @@ -146,7 +150,7 @@ describe('watchman watch', () => { ['anyof', ['dirname', 'fruits'], ['dirname', 'vegetables']], ]); - expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms']); + expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms', 'size']); expect(query[2].glob).toEqual([ 'fruits/**/*.js', @@ -181,6 +185,7 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 33}, name: 'vegetables/durian.zip', + size: 43, }, ], is_fresh_instance: true, @@ -206,8 +211,8 @@ describe('watchman watch', () => { }).then(data => { expect(data.files).toEqual( createMap({ - [path.join(DURIAN_RELATIVE, 'foo.1.js')]: ['', 33, 0, [], null], - [path.join(DURIAN_RELATIVE, 'foo.2.js')]: ['', 33, 0, [], null], + [path.join(DURIAN_RELATIVE, 'foo.1.js')]: ['', 33, 43, 0, [], null], + [path.join(DURIAN_RELATIVE, 'foo.2.js')]: ['', 33, 43, 0, [], null], }), ); }); @@ -228,11 +233,13 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 42}, name: 'fruits/kiwi.js', + size: 40, }, { exists: false, mtime_ms: null, name: 'fruits/tomato.js', + size: 0, }, ], is_fresh_instance: false, @@ -267,9 +274,9 @@ describe('watchman watch', () => { expect(data.files).toEqual( createMap({ - [KIWI_RELATIVE]: ['', 42, 0, [], null], - [MELON_RELATIVE]: ['', 33, 0, [], null], - [STRAWBERRY_RELATIVE]: ['', 30, 0, [], null], + [KIWI_RELATIVE]: ['', 42, 40, 0, [], null], + [MELON_RELATIVE]: ['', 33, 43, 0, [], null], + [STRAWBERRY_RELATIVE]: ['', 30, 40, 0, [], null], }), ); }); @@ -292,17 +299,20 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 42}, name: 'fruits/kiwi.js', + size: 52, }, { exists: true, mtime_ms: {toNumber: () => 41}, name: 'fruits/banana.js', + size: 51, }, { 'content.sha1hex': mockTomatoSha1, exists: true, mtime_ms: {toNumber: () => 76}, name: 'fruits/tomato.js', + size: 41, }, ], is_fresh_instance: true, @@ -312,9 +322,9 @@ describe('watchman watch', () => { 'watch-project': WATCH_PROJECT_MOCK, }; - const mockBananaMetadata = ['Banana', 41, 1, ['Raspberry'], null]; + const mockBananaMetadata = ['Banana', 41, 51, 1, ['Raspberry'], null]; mockFiles.set(BANANA_RELATIVE, mockBananaMetadata); - const mockTomatoMetadata = ['Tomato', 31, 1, [], mockTomatoSha1]; + const mockTomatoMetadata = ['Tomato', 31, 41, 1, [], mockTomatoSha1]; mockFiles.set(TOMATO_RELATIVE, mockTomatoMetadata); const clocks = createMap({ @@ -344,8 +354,8 @@ describe('watchman watch', () => { expect(data.files).toEqual( createMap({ [BANANA_RELATIVE]: mockBananaMetadata, - [KIWI_RELATIVE]: ['', 42, 0, [], null], - [TOMATO_RELATIVE]: ['Tomato', 76, 1, [], mockTomatoSha1], + [KIWI_RELATIVE]: ['', 42, 52, 0, [], null], + [TOMATO_RELATIVE]: ['Tomato', 76, 41, 1, [], mockTomatoSha1], }), ); @@ -373,6 +383,7 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 42}, name: 'kiwi.js', + size: 52, }, ], is_fresh_instance: false, @@ -385,6 +396,7 @@ describe('watchman watch', () => { exists: true, mtime_ms: {toNumber: () => 33}, name: 'melon.json', + size: 43, }, ], is_fresh_instance: true, @@ -425,8 +437,8 @@ describe('watchman watch', () => { expect(data.files).toEqual( createMap({ - [KIWI_RELATIVE]: ['', 42, 0, [], null], - [MELON_RELATIVE]: ['', 33, 0, [], null], + [KIWI_RELATIVE]: ['', 42, 52, 0, [], null], + [MELON_RELATIVE]: ['', 33, 43, 0, [], null], }), ); }); @@ -493,7 +505,7 @@ describe('watchman watch', () => { ['anyof', ['suffix', 'js'], ['suffix', 'json']], ]); - expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms']); + expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms', 'size']); expect(query[2].glob).toEqual(['**/*.js', '**/*.json']); diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index e61a06f714e1..d182556dcb12 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -16,7 +16,9 @@ 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; +type Callback = ( + result: Array<[/* id */ string, /* mtime */ number, /* size */ number]>, +) => void; function find( roots: Array, @@ -51,7 +53,7 @@ function find( } else { const ext = path.extname(file).substr(1); if (extensions.indexOf(ext) !== -1) { - result.push([file, stat.mtime.getTime()]); + result.push([file, stat.mtime.getTime(), stat.size]); } } } @@ -114,7 +116,7 @@ function findNative( lines.forEach(path => { fs.stat(path, (err, stat) => { if (!err && stat) { - result.push([path, stat.mtime.getTime()]); + result.push([path, stat.mtime.getTime(), stat.size]); } if (--count === 0) { callback(result); @@ -148,12 +150,13 @@ export default function nodeCrawl( const filePath = fileData[0]; const relativeFilePath = fastPath.relative(rootDir, filePath); const mtime = fileData[1]; + const size = fileData[2]; const existingFile = data.files.get(relativeFilePath); if (existingFile && existingFile[H.MTIME] === mtime) { files.set(relativeFilePath, existingFile); } else { // See ../constants.js; SHA-1 will always be null and fulfilled later. - files.set(relativeFilePath, ['', mtime, 0, [], null]); + files.set(relativeFilePath, ['', mtime, size, 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 f6b1f5830184..4728c120c320 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -29,7 +29,7 @@ function WatchmanError(error: Error): Error { export default async function watchmanCrawl( options: CrawlerOptions, ): Promise { - const fields = ['name', 'exists', 'mtime_ms']; + const fields = ['name', 'exists', 'mtime_ms', 'size']; const {data, extensions, ignore, rootDir, roots} = options; const defaultWatchExpression = [ 'allof', @@ -173,6 +173,7 @@ export default async function watchmanCrawl( typeof fileData.mtime_ms === 'number' ? fileData.mtime_ms : fileData.mtime_ms.toNumber(); + const size = fileData.size; let sha1hex = fileData['content.sha1hex']; if (typeof sha1hex !== 'string' || sha1hex.length !== 40) { @@ -193,7 +194,7 @@ export default async function watchmanCrawl( nextData[1] = mtime; } else { // See ../constants.js - nextData = ['', mtime, 0, [], sha1hex]; + nextData = ['', mtime, size, 0, [], sha1hex]; } const mappings = options.mapper ? options.mapper(filePath) : null; diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index f2b8021a2907..65cfe50d92d2 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -170,6 +170,7 @@ const getWhiteList = (list: ?Array): ?RegExp => { * type FileMetaData = { * id: ?string, // used to look up module metadata objects in `map`. * mtime: number, // check for outdated files. + * size: number, // size of the file in bytes. * visited: boolean, // whether the file has been parsed or not. * dependencies: Array, // all relative dependencies of this file. * sha1: ?string, // SHA-1 of the file, if requested via options. @@ -188,8 +189,8 @@ const getWhiteList = (list: ?Array): ?RegExp => { * * Note that the data structures described above are conceptual only. The actual * implementation uses arrays and constant keys for metadata storage. Instead of - * `{id: 'flatMap', mtime: 3421, visited: true, dependencies: []}` the real - * representation is similar to `['flatMap', 3421, 1, []]` to save storage space + * `{id: 'flatMap', mtime: 3421, size: 42, visited: true, dependencies: []}` the real + * representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space * and reduce parse and write time of a big JSON blob. * * The HasteMap is created as follows: @@ -892,7 +893,14 @@ class HasteMap extends EventEmitter { stat, 'since the file exists or changed, it should have stats', ); - const fileMetadata = ['', stat.mtime.getTime(), 0, [], null]; + const fileMetadata = [ + '', + stat.mtime.getTime(), + stat.size, + 0, + [], + null, + ]; hasteMap.files.set(relativeFilePath, fileMetadata); const promise = this._processFile( hasteMap, diff --git a/types/HasteMap.js b/types/HasteMap.js index 88815cd8ef2e..6c3d59d8afd9 100644 --- a/types/HasteMap.js +++ b/types/HasteMap.js @@ -51,6 +51,7 @@ export type RawModuleMap = {| export type FileMetaData = [ /* id */ string, /* mtime */ number, + /* size */ number, /* visited */ 0 | 1, /* dependencies */ Array, /* sha1 */ ?string, @@ -62,8 +63,9 @@ export type ModuleMetaData = [Path, /* type */ number]; export type HType = {| ID: 0, MTIME: 1, - VISITED: 2, - DEPENDENCIES: 3, + SIZE: 2, + VISITED: 3, + DEPENDENCIES: 4, PATH: 0, TYPE: 1, MODULE: 0, @@ -72,4 +74,4 @@ export type HType = {| NATIVE_PLATFORM: 'native', |}; -export type HTypeValue = 0 | 1 | 2 | 3 | 'g'; +export type HTypeValue = $Values;