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 069905a
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 10 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -22,7 +22,7 @@

### Performance

- `[jest-core]` Cache micromatch in SearchSource globsToMatcher and avoid recreating RegExp instances in regexToMatcher ([#10131](https://github.com/facebook/jest/pull/10131))
- `[jest-core, jest-transform]` Improve Jest startup time and test runtime, particularly when running with coverage, by caching micromatch and avoiding recreating RegExp instances ([#10131](https://github.com/facebook/jest/pull/10131))

## 26.0.1

Expand Down
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 069905a

Please sign in to comment.