Skip to content

Commit

Permalink
Improve performance of SearchSource.findMatchingTests by 15% (#8184)
Browse files Browse the repository at this point in the history
* Improve performance of SearchSource.findMatchingTests

* Review feedback.

* Update CHANGELOG.md
  • Loading branch information
scotthovestadt committed Mar 21, 2019
1 parent 800e6fb commit 742018a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 76 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -34,6 +34,7 @@

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

## 24.5.0
Expand Down
110 changes: 52 additions & 58 deletions packages/jest-core/src/SearchSource.ts
Expand Up @@ -16,13 +16,7 @@ import {escapePathForRegex} from 'jest-regex-util';
import {replaceRootDirInPath} from 'jest-config';
import {buildSnapshotResolver} from 'jest-snapshot';
import {replacePathSepForGlob, testPathPatternToRegExp} from 'jest-util';
import {
Stats,
TestPathCases,
TestPathCasesWithPathPattern,
TestPathCaseWithPathPatternStats,
Filter,
} from './types';
import {TestPathCases, Filter, Stats} from './types';

export type SearchResult = {
noSCM?: boolean;
Expand All @@ -42,23 +36,11 @@ export type TestSelectionConfig = {
watch?: boolean;
};

const globsToMatcher = (globs?: Array<Config.Glob> | null) => {
if (globs == null || globs.length === 0) {
return () => true;
}
const globsToMatcher = (globs: Array<Config.Glob>) => (path: Config.Path) =>
micromatch.some(replacePathSepForGlob(path), globs, {dot: true});

return (path: Config.Path) =>
micromatch.some(replacePathSepForGlob(path), globs, {dot: true});
};

const regexToMatcher = (testRegex: Array<string>) => {
if (!testRegex.length) {
return () => true;
}

return (path: Config.Path) =>
testRegex.some(testRegex => new RegExp(testRegex).test(path));
};
const regexToMatcher = (testRegex: Array<string>) => (path: Config.Path) =>
testRegex.some(testRegex => new RegExp(testRegex).test(path));

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

export default class SearchSource {
private _context: Context;
private _rootPattern: RegExp;
private _testIgnorePattern: RegExp | null;
private _testPathCases: TestPathCases;
private _testPathCases: TestPathCases = [];

constructor(context: Context) {
const {config} = context;
this._context = context;
this._rootPattern = new RegExp(

const rootPattern = new RegExp(
config.roots.map(dir => escapePathForRegex(dir + path.sep)).join('|'),
);
this._testPathCases.push({
isMatch: path => rootPattern.test(path),
stat: 'roots',
});

const ignorePattern = config.testPathIgnorePatterns;
this._testIgnorePattern = ignorePattern.length
? new RegExp(ignorePattern.join('|'))
: null;

this._testPathCases = {
roots: path => this._rootPattern.test(path),
testMatch: globsToMatcher(config.testMatch),
testPathIgnorePatterns: path =>
!this._testIgnorePattern || !this._testIgnorePattern.test(path),
testRegex: regexToMatcher(config.testRegex),
};
if (config.testMatch.length) {
this._testPathCases.push({
isMatch: globsToMatcher(config.testMatch),
stat: 'testMatch',
});
}

if (config.testPathIgnorePatterns.length) {
const testIgnorePatternsRegex = new RegExp(
config.testPathIgnorePatterns.join('|'),
);
this._testPathCases.push({
isMatch: path => !testIgnorePatternsRegex.test(path),
stat: 'testPathIgnorePatterns',
});
}

if (config.testRegex.length) {
this._testPathCases.push({
isMatch: regexToMatcher(config.testRegex),
stat: 'testRegex',
});
}
}

private _filterTestPathsWithStats(
Expand All @@ -113,27 +109,27 @@ export default class SearchSource {
total: allPaths.length,
};

const testCases = Object.assign({}, this._testPathCases);
const testCases = Array.from(this._testPathCases); // clone
if (testPathPattern) {
const regex = testPathPatternToRegExp(testPathPattern);
(testCases as TestPathCasesWithPathPattern).testPathPattern = (
path: Config.Path,
) => regex.test(path);
(data.stats as TestPathCaseWithPathPatternStats).testPathPattern = 0;
testCases.push({
isMatch: (path: Config.Path) => regex.test(path),
stat: 'testPathPattern',
});
data.stats.testPathPattern = 0;
}

const testCasesKeys = Object.keys(testCases) as Array<keyof Stats>;
data.tests = allPaths.filter(test =>
testCasesKeys.reduce<boolean>((flag, key) => {
const {stats} = data;
if (testCases[key](test.path)) {
stats[key] = stats[key] === undefined ? 1 : stats[key] + 1;
return flag && true;
data.tests = allPaths.filter(test => {
let filterResult = true;
for (const {isMatch, stat} of testCases) {
if (isMatch(test.path)) {
data.stats[stat]!++;
} else {
filterResult = false;
}
stats[key] = stats[key] || 0;
return false;
}, true),
);
}
return filterResult;
});

return data;
}
Expand All @@ -146,9 +142,7 @@ export default class SearchSource {
}

isTestFilePath(path: Config.Path): boolean {
return (Object.keys(this._testPathCases) as Array<
keyof TestPathCases
>).every(key => this._testPathCases[key](path));
return this._testPathCases.every(testCase => testCase.isMatch(path));
}

findMatchingTests(testPathPattern?: string): SearchResult {
Expand Down
8 changes: 5 additions & 3 deletions packages/jest-core/src/getNoTestFoundVerbose.ts
Expand Up @@ -17,10 +17,12 @@ export default function getNoTestFoundVerbose(
if (key === 'roots' && config.roots.length === 1) {
return null;
}
const value = config[key];
const value = (config as {[key: string]: unknown})[key];
if (value) {
const valueAsString = Array.isArray(value) ? value.join(', ') : value;
const matches = pluralize('match', stats[key], 'es');
const valueAsString = Array.isArray(value)
? value.join(', ')
: String(value);
const matches = pluralize('match', stats[key] || 0, 'es');
return ` ${key}: ${chalk.yellow(valueAsString)} - ${matches}`;
}
return null;
Expand Down
27 changes: 12 additions & 15 deletions packages/jest-core/src/types.ts
Expand Up @@ -9,6 +9,14 @@ import {Context} from 'jest-runtime';
import {Test} from 'jest-runner';
import {Config} from '@jest/types';

export type Stats = {
roots: number;
testMatch: number;
testPathIgnorePatterns: number;
testRegex: number;
testPathPattern?: number;
};

export type TestRunData = Array<{
context: Context;
matches: {
Expand All @@ -19,21 +27,10 @@ export type TestRunData = Array<{
};
}>;

type TestPathCaseStats = Record<keyof (TestPathCases), number>;

export type TestPathCaseWithPathPatternStats = Record<
keyof (TestPathCasesWithPathPattern),
number
>;

export type Stats = TestPathCaseStats | TestPathCaseWithPathPatternStats;

export type TestPathCases = {
roots: (path: Config.Path) => boolean;
testMatch: (path: Config.Path) => boolean;
testPathIgnorePatterns: (path: Config.Path) => boolean;
testRegex: (path: Config.Path) => boolean;
};
export type TestPathCases = Array<{
stat: keyof Stats;
isMatch: (path: Config.Path) => boolean;
}>;

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

0 comments on commit 742018a

Please sign in to comment.