Skip to content

Commit

Permalink
[jest-haste-map] Use more optimal suffix-set format (#11784)
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanBacon authored Aug 27, 2021
1 parent c8b8ce2 commit d455d2d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784))
- `[jest-cli]` Adds a new config options `snapshotFormat` which offers a way to override any of the formatting settings which come with [pretty-format](https://www.npmjs.com/package/pretty-format#usage-with-options). ([#11654](https://github.com/facebook/jest/pull/11654))

### Fixes
Expand Down
12 changes: 10 additions & 2 deletions packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const path = require('path');
jest.mock('fb-watchman', () => {
const normalizePathSep = require('../../lib/normalizePathSep').default;
const Client = jest.fn();
Client.prototype.capabilityCheck = jest.fn((args, callback) =>
setImmediate(() => {
callback(null, {
capabilities: {'suffix-set': true},
version: '2021.06.07.00',
});
}),
);
Client.prototype.command = jest.fn((args, callback) =>
setImmediate(() => {
const path = args[1] ? normalizePathSep(args[1]) : undefined;
Expand Down Expand Up @@ -145,7 +153,7 @@ describe('watchman watch', () => {
expect(query[2].expression).toEqual([
'allof',
['type', 'f'],
['anyof', ['suffix', 'js'], ['suffix', 'json']],
['suffix', ['js', 'json']],
['anyof', ['dirname', 'fruits'], ['dirname', 'vegetables']],
]);

Expand Down Expand Up @@ -488,7 +496,7 @@ describe('watchman watch', () => {
expect(query[2].expression).toEqual([
'allof',
['type', 'f'],
['anyof', ['suffix', 'js'], ['suffix', 'json']],
['suffix', ['js', 'json']],
]);

expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms', 'size']);
Expand Down
59 changes: 54 additions & 5 deletions packages/jest-haste-map/src/crawlers/watchman.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type WatchmanListCapabilitiesResponse = {
capabilities: Array<string>;
};

type WatchmanCapabilityCheckResponse = {
// { 'suffix-set': true }
capabilities: Record<string, boolean>;
// '2021.06.07.00'
version: string;
};

type WatchmanWatchProjectResponse = {
watch: string;
relative_path: string;
Expand Down Expand Up @@ -57,21 +64,63 @@ function WatchmanError(error: Error): Error {
return error;
}

/**
* Wrap watchman capabilityCheck method as a promise.
*
* @param client watchman client
* @param caps capabilities to verify
* @returns a promise resolving to a list of verified capabilities
*/
async function capabilityCheck(
client: watchman.Client,
caps: Partial<watchman.Capabilities>,
): Promise<WatchmanCapabilityCheckResponse> {
return new Promise((resolve, reject) => {
client.capabilityCheck(
// @ts-expect-error: incorrectly typed
caps,
(error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
},
);
});
}

export = async function watchmanCrawl(options: CrawlerOptions): Promise<{
changedFiles?: FileData;
removedFiles: FileData;
hasteMap: InternalHasteMap;
}> {
const fields = ['name', 'exists', 'mtime_ms', 'size'];
const {data, extensions, ignore, rootDir, roots} = options;
const defaultWatchExpression = [
'allof',
['type', 'f'],
['anyof', ...extensions.map(extension => ['suffix', extension])],
];
const defaultWatchExpression: Array<any> = ['allof', ['type', 'f']];
const clocks = data.clocks;
const client = new watchman.Client();

// https://facebook.github.io/watchman/docs/capabilities.html
// Check adds about ~28ms
const capabilities = await capabilityCheck(client, {
// If a required capability is missing then an error will be thrown,
// we don't need this assertion, so using optional instead.
optional: ['suffix-set'],
});

if (capabilities?.capabilities['suffix-set']) {
// If available, use the optimized `suffix-set` operation:
// https://facebook.github.io/watchman/docs/expr/suffix.html#suffix-set
defaultWatchExpression.push(['suffix', extensions]);
} else {
// Otherwise use the older and less optimal suffix tuple array
defaultWatchExpression.push([
'anyof',
...extensions.map(extension => ['suffix', extension]),
]);
}

let clientError;
client.on('error', error => (clientError = WatchmanError(error)));

Expand Down

0 comments on commit d455d2d

Please sign in to comment.