Skip to content

Commit

Permalink
Optimize micromatch and RegExps in shouldInstrument
Browse files Browse the repository at this point in the history
I've been profiling running Jest with code coverage at Airbnb, and
noticed that shouldInstrument is called often and is fairly
expensive. It also seems to call micromatch and `new RegExp`
repeatedly, both of which can be optimized by caching the work to
convert globs and strings into matchers and regexes.

I profiled this change by running a set of 27 fairly simple tests.
Before this change, about 6-7 seconds was spent in shouldInstrument.
After this change, only 400-500 ms is spent there. I would expect
this delta to increase along with the number of tests and size of
their dependency graphs.
  • Loading branch information
lencioni committed Jun 8, 2020
1 parent 39120d9 commit 5669574
Showing 1 changed file with 22 additions and 9 deletions.
31 changes: 22 additions & 9 deletions packages/jest-transform/src/shouldInstrument.ts
Expand Up @@ -8,14 +8,28 @@
import * as path from 'path';
import type {Config} from '@jest/types';
import {escapePathForRegex} from 'jest-regex-util';
import {replacePathSepForGlob} from 'jest-util';
import {globsToMatcher, replacePathSepForGlob} from 'jest-util';
import micromatch = require('micromatch');
import type {ShouldInstrumentOptions} from './types';

const MOCKS_PATTERN = new RegExp(
escapePathForRegex(path.sep + '__mocks__' + path.sep),
);

const cachedRegexes = new Map();
const getRegex = (regexStr: string) => {
if (!cachedRegexes.has(regexStr)) {
cachedRegexes.set(regexStr, new RegExp(regexStr));
}

const regex = cachedRegexes.get(regexStr);

// prevent stateful regexes from breaking, just in case
regex.lastIndex = 0;

return regex;
};

export default function shouldInstrument(
filename: Config.Path,
options: ShouldInstrumentOptions,
Expand All @@ -33,15 +47,15 @@ export default function shouldInstrument(
}

if (
!config.testPathIgnorePatterns.some(pattern => !!filename.match(pattern))
!config.testPathIgnorePatterns.some(pattern =>
getRegex(pattern).test(filename),
)
) {
if (config.testRegex.some(regex => new RegExp(regex).test(filename))) {
return false;
}

if (
micromatch([replacePathSepForGlob(filename)], config.testMatch).length
) {
if (globsToMatcher(config.testMatch)(replacePathSepForGlob(filename))) {
return false;
}
}
Expand All @@ -59,10 +73,9 @@ export default function shouldInstrument(
// still cover if `only` is specified
!options.collectCoverageOnlyFrom &&
options.collectCoverageFrom.length &&
micromatch(
[replacePathSepForGlob(path.relative(config.rootDir, filename))],
options.collectCoverageFrom,
).length === 0
!globsToMatcher(options.collectCoverageFrom)(
replacePathSepForGlob(path.relative(config.rootDir, filename)),
)
) {
return false;
}
Expand Down

0 comments on commit 5669574

Please sign in to comment.