Skip to content

Commit 742018a

Browse files
Improve performance of SearchSource.findMatchingTests by 15% (#8184)
* Improve performance of SearchSource.findMatchingTests * Review feedback. * Update CHANGELOG.md
1 parent 800e6fb commit 742018a

File tree

4 files changed

+70
-76
lines changed

4 files changed

+70
-76
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
- `[jest-haste-map]` Optimize haste map data structure for serialization/deserialization ([#8171](https://github.com/facebook/jest/pull/8171))
3636
- `[jest-haste-map]` Avoid persisting haste map or processing files when not changed ([#8153](https://github.com/facebook/jest/pull/8153))
37+
- `[jest-core]` Improve performance of SearchSource.findMatchingTests by 15% ([#8184](https://github.com/facebook/jest/pull/8184))
3738
- `[jest-resolve]` Optimize internal cache lookup performance ([#8183](https://github.com/facebook/jest/pull/8183))
3839

3940
## 24.5.0

packages/jest-core/src/SearchSource.ts

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ import {escapePathForRegex} from 'jest-regex-util';
1616
import {replaceRootDirInPath} from 'jest-config';
1717
import {buildSnapshotResolver} from 'jest-snapshot';
1818
import {replacePathSepForGlob, testPathPatternToRegExp} from 'jest-util';
19-
import {
20-
Stats,
21-
TestPathCases,
22-
TestPathCasesWithPathPattern,
23-
TestPathCaseWithPathPatternStats,
24-
Filter,
25-
} from './types';
19+
import {TestPathCases, Filter, Stats} from './types';
2620

2721
export type SearchResult = {
2822
noSCM?: boolean;
@@ -42,23 +36,11 @@ export type TestSelectionConfig = {
4236
watch?: boolean;
4337
};
4438

45-
const globsToMatcher = (globs?: Array<Config.Glob> | null) => {
46-
if (globs == null || globs.length === 0) {
47-
return () => true;
48-
}
39+
const globsToMatcher = (globs: Array<Config.Glob>) => (path: Config.Path) =>
40+
micromatch.some(replacePathSepForGlob(path), globs, {dot: true});
4941

50-
return (path: Config.Path) =>
51-
micromatch.some(replacePathSepForGlob(path), globs, {dot: true});
52-
};
53-
54-
const regexToMatcher = (testRegex: Array<string>) => {
55-
if (!testRegex.length) {
56-
return () => true;
57-
}
58-
59-
return (path: Config.Path) =>
60-
testRegex.some(testRegex => new RegExp(testRegex).test(path));
61-
};
42+
const regexToMatcher = (testRegex: Array<string>) => (path: Config.Path) =>
43+
testRegex.some(testRegex => new RegExp(testRegex).test(path));
6244

6345
const toTests = (context: Context, tests: Array<Config.Path>) =>
6446
tests.map(path => ({
@@ -69,29 +51,43 @@ const toTests = (context: Context, tests: Array<Config.Path>) =>
6951

7052
export default class SearchSource {
7153
private _context: Context;
72-
private _rootPattern: RegExp;
73-
private _testIgnorePattern: RegExp | null;
74-
private _testPathCases: TestPathCases;
54+
private _testPathCases: TestPathCases = [];
7555

7656
constructor(context: Context) {
7757
const {config} = context;
7858
this._context = context;
79-
this._rootPattern = new RegExp(
59+
60+
const rootPattern = new RegExp(
8061
config.roots.map(dir => escapePathForRegex(dir + path.sep)).join('|'),
8162
);
63+
this._testPathCases.push({
64+
isMatch: path => rootPattern.test(path),
65+
stat: 'roots',
66+
});
8267

83-
const ignorePattern = config.testPathIgnorePatterns;
84-
this._testIgnorePattern = ignorePattern.length
85-
? new RegExp(ignorePattern.join('|'))
86-
: null;
87-
88-
this._testPathCases = {
89-
roots: path => this._rootPattern.test(path),
90-
testMatch: globsToMatcher(config.testMatch),
91-
testPathIgnorePatterns: path =>
92-
!this._testIgnorePattern || !this._testIgnorePattern.test(path),
93-
testRegex: regexToMatcher(config.testRegex),
94-
};
68+
if (config.testMatch.length) {
69+
this._testPathCases.push({
70+
isMatch: globsToMatcher(config.testMatch),
71+
stat: 'testMatch',
72+
});
73+
}
74+
75+
if (config.testPathIgnorePatterns.length) {
76+
const testIgnorePatternsRegex = new RegExp(
77+
config.testPathIgnorePatterns.join('|'),
78+
);
79+
this._testPathCases.push({
80+
isMatch: path => !testIgnorePatternsRegex.test(path),
81+
stat: 'testPathIgnorePatterns',
82+
});
83+
}
84+
85+
if (config.testRegex.length) {
86+
this._testPathCases.push({
87+
isMatch: regexToMatcher(config.testRegex),
88+
stat: 'testRegex',
89+
});
90+
}
9591
}
9692

9793
private _filterTestPathsWithStats(
@@ -113,27 +109,27 @@ export default class SearchSource {
113109
total: allPaths.length,
114110
};
115111

116-
const testCases = Object.assign({}, this._testPathCases);
112+
const testCases = Array.from(this._testPathCases); // clone
117113
if (testPathPattern) {
118114
const regex = testPathPatternToRegExp(testPathPattern);
119-
(testCases as TestPathCasesWithPathPattern).testPathPattern = (
120-
path: Config.Path,
121-
) => regex.test(path);
122-
(data.stats as TestPathCaseWithPathPatternStats).testPathPattern = 0;
115+
testCases.push({
116+
isMatch: (path: Config.Path) => regex.test(path),
117+
stat: 'testPathPattern',
118+
});
119+
data.stats.testPathPattern = 0;
123120
}
124121

125-
const testCasesKeys = Object.keys(testCases) as Array<keyof Stats>;
126-
data.tests = allPaths.filter(test =>
127-
testCasesKeys.reduce<boolean>((flag, key) => {
128-
const {stats} = data;
129-
if (testCases[key](test.path)) {
130-
stats[key] = stats[key] === undefined ? 1 : stats[key] + 1;
131-
return flag && true;
122+
data.tests = allPaths.filter(test => {
123+
let filterResult = true;
124+
for (const {isMatch, stat} of testCases) {
125+
if (isMatch(test.path)) {
126+
data.stats[stat]!++;
127+
} else {
128+
filterResult = false;
132129
}
133-
stats[key] = stats[key] || 0;
134-
return false;
135-
}, true),
136-
);
130+
}
131+
return filterResult;
132+
});
137133

138134
return data;
139135
}
@@ -146,9 +142,7 @@ export default class SearchSource {
146142
}
147143

148144
isTestFilePath(path: Config.Path): boolean {
149-
return (Object.keys(this._testPathCases) as Array<
150-
keyof TestPathCases
151-
>).every(key => this._testPathCases[key](path));
145+
return this._testPathCases.every(testCase => testCase.isMatch(path));
152146
}
153147

154148
findMatchingTests(testPathPattern?: string): SearchResult {

packages/jest-core/src/getNoTestFoundVerbose.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ export default function getNoTestFoundVerbose(
1717
if (key === 'roots' && config.roots.length === 1) {
1818
return null;
1919
}
20-
const value = config[key];
20+
const value = (config as {[key: string]: unknown})[key];
2121
if (value) {
22-
const valueAsString = Array.isArray(value) ? value.join(', ') : value;
23-
const matches = pluralize('match', stats[key], 'es');
22+
const valueAsString = Array.isArray(value)
23+
? value.join(', ')
24+
: String(value);
25+
const matches = pluralize('match', stats[key] || 0, 'es');
2426
return ` ${key}: ${chalk.yellow(valueAsString)} - ${matches}`;
2527
}
2628
return null;

packages/jest-core/src/types.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import {Context} from 'jest-runtime';
99
import {Test} from 'jest-runner';
1010
import {Config} from '@jest/types';
1111

12+
export type Stats = {
13+
roots: number;
14+
testMatch: number;
15+
testPathIgnorePatterns: number;
16+
testRegex: number;
17+
testPathPattern?: number;
18+
};
19+
1220
export type TestRunData = Array<{
1321
context: Context;
1422
matches: {
@@ -19,21 +27,10 @@ export type TestRunData = Array<{
1927
};
2028
}>;
2129

22-
type TestPathCaseStats = Record<keyof (TestPathCases), number>;
23-
24-
export type TestPathCaseWithPathPatternStats = Record<
25-
keyof (TestPathCasesWithPathPattern),
26-
number
27-
>;
28-
29-
export type Stats = TestPathCaseStats | TestPathCaseWithPathPatternStats;
30-
31-
export type TestPathCases = {
32-
roots: (path: Config.Path) => boolean;
33-
testMatch: (path: Config.Path) => boolean;
34-
testPathIgnorePatterns: (path: Config.Path) => boolean;
35-
testRegex: (path: Config.Path) => boolean;
36-
};
30+
export type TestPathCases = Array<{
31+
stat: keyof Stats;
32+
isMatch: (path: Config.Path) => boolean;
33+
}>;
3734

3835
export type TestPathCasesWithPathPattern = TestPathCases & {
3936
testPathPattern: (path: Config.Path) => boolean;

0 commit comments

Comments
 (0)