diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ca2d37dda1..64737724c07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/jest-core/src/SearchSource.ts b/packages/jest-core/src/SearchSource.ts index 99f03b29781b..5e9a8d330a80 100644 --- a/packages/jest-core/src/SearchSource.ts +++ b/packages/jest-core/src/SearchSource.ts @@ -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; @@ -42,23 +36,11 @@ export type TestSelectionConfig = { watch?: boolean; }; -const globsToMatcher = (globs?: Array | null) => { - if (globs == null || globs.length === 0) { - return () => true; - } +const globsToMatcher = (globs: Array) => (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) => { - if (!testRegex.length) { - return () => true; - } - - return (path: Config.Path) => - testRegex.some(testRegex => new RegExp(testRegex).test(path)); -}; +const regexToMatcher = (testRegex: Array) => (path: Config.Path) => + testRegex.some(testRegex => new RegExp(testRegex).test(path)); const toTests = (context: Context, tests: Array) => tests.map(path => ({ @@ -69,29 +51,43 @@ const toTests = (context: Context, tests: Array) => 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( @@ -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; - data.tests = allPaths.filter(test => - testCasesKeys.reduce((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; } @@ -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 { diff --git a/packages/jest-core/src/getNoTestFoundVerbose.ts b/packages/jest-core/src/getNoTestFoundVerbose.ts index e1389f7ef423..5e0177a9c3d1 100644 --- a/packages/jest-core/src/getNoTestFoundVerbose.ts +++ b/packages/jest-core/src/getNoTestFoundVerbose.ts @@ -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; diff --git a/packages/jest-core/src/types.ts b/packages/jest-core/src/types.ts index 9e57b7039c6c..04fd908b70b2 100644 --- a/packages/jest-core/src/types.ts +++ b/packages/jest-core/src/types.ts @@ -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: { @@ -19,21 +27,10 @@ export type TestRunData = Array<{ }; }>; -type TestPathCaseStats = Record; - -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;