Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of SearchSource.findMatchingTests by 15% #8184

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
110 changes: 53 additions & 57 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;
}

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

const regexToMatcher = (testRegex: Array<string>) => {
if (!testRegex.length) {
return () => true;
}
const globsToMatcher = (globs: Array<Config.Glob>) => (path: Config.Path) =>
micromatch.some(replacePathSepForGlob(path), globs, {dot: 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,45 @@ 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;
scotthovestadt marked this conversation as resolved.
Show resolved Hide resolved

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

this._testPathCases = [];

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 +111,27 @@ export default class SearchSource {
total: allPaths.length,
};

const testCases = Object.assign({}, this._testPathCases);
const testCases = this._testPathCases.slice(0);
scotthovestadt marked this conversation as resolved.
Show resolved Hide resolved
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;
scotthovestadt marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
scotthovestadt marked this conversation as resolved.
Show resolved Hide resolved
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 +144,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