Skip to content

Commit

Permalink
Haste map optimizations (#1289)
Browse files Browse the repository at this point in the history
* Start refactoring jest-haste-map.

* Create `jest-file-exists` and `jest-resolve-dependencies`.

* Flow fixes.
  • Loading branch information
cpojer committed Jul 15, 2016
1 parent c2ee580 commit 4cf8713
Show file tree
Hide file tree
Showing 27 changed files with 431 additions and 313 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"eslint": "^3.0.1",
"eslint-plugin-babel": "^3.3.0",
"fbjs-scripts": "^0.7.1",
"flow-bin": "^0.28.0",
"flow-bin": "^0.29.0",
"glob": "^7.0.4",
"graceful-fs": "^4.1.4",
"lerna": "2.0.0-beta.24",
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"jest-changed-files": "^13.2.2",
"jest-config": "^13.2.3",
"jest-environment-jsdom": "^13.2.2",
"jest-file-exists": "^13.2.2",
"jest-haste-map": "^13.2.2",
"jest-jasmine2": "^13.2.3",
"jest-mock": "^13.2.2",
"jest-resolve-dependencies": "^13.2.2",
"jest-resolve": "^13.2.2",
"jest-runtime": "^13.2.3",
"jest-snapshot": "^13.2.3",
Expand Down
64 changes: 33 additions & 31 deletions packages/jest-cli/src/SearchSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@

'use strict';

import type {HasteResolverContext} from 'types/Runtime';
import type {HasteContext} from 'types/HasteMap';
import type {Path} from 'types/Config';
import type {ResolveModuleConfig} from '../../jest-resolve/src';

const Resolver = require('jest-resolve');
const DependencyResolver = require('jest-resolve-dependencies');

const chalk = require('chalk');
const changedFiles = require('jest-changed-files');
const fileExists = require('jest-file-exists');
const path = require('path');
const utils = require('jest-util');

Expand Down Expand Up @@ -58,7 +59,7 @@ const pluralize = (
) => `${count} ${word}${count === 1 ? '' : ending}`;

class SearchSource {
_hasteMap: Promise<HasteResolverContext>;
_hasteContext: HasteContext;
_config: SearchSourceConfig;
_options: ResolveModuleConfig;
_testPathDirPattern: RegExp;
Expand All @@ -71,11 +72,11 @@ class SearchSource {
};

constructor(
hasteMap: Promise<HasteResolverContext>,
hasteMap: HasteContext,
config: SearchSourceConfig,
options?: ResolveModuleConfig,
) {
this._hasteMap = hasteMap;
this._hasteContext = hasteMap;
this._config = config;
this._options = options || {};

Expand Down Expand Up @@ -129,13 +130,11 @@ class SearchSource {

_getAllTestPaths(
testPathPattern: StrOrRegExpPattern,
): Promise<SearchResult> {
return this._hasteMap.then(data => (
this._filterTestPathsWithStats(
Object.keys(data.moduleMap.files),
testPathPattern,
)
));
): SearchResult {
return this._filterTestPathsWithStats(
Object.keys(this._hasteContext.moduleMap.files),
testPathPattern,
);
}

isTestFilePath(path: Path): boolean {
Expand All @@ -146,33 +145,34 @@ class SearchSource {

findMatchingTests(
testPathPattern: StrOrRegExpPattern,
): Promise<SearchResult> {
): SearchResult {
if (testPathPattern && !(testPathPattern instanceof RegExp)) {
const maybeFile = path.resolve(process.cwd(), testPathPattern);
if (Resolver.fileExists(maybeFile)) {
return Promise.resolve(
this._filterTestPathsWithStats([maybeFile]),
);
if (fileExists(maybeFile, this._hasteContext.moduleMap.files)) {
return this._filterTestPathsWithStats([maybeFile]);
}
}

return this._getAllTestPaths(testPathPattern);
}

findRelatedTests(allPaths: Set<Path>): Promise<SearchResult> {
return this._hasteMap
.then(data => ({
paths: data.resolver.resolveInverseDependencies(
allPaths,
this.isTestFilePath.bind(this),
{
skipNodeResolution: this._options.skipNodeResolution,
},
),
}));
findRelatedTests(allPaths: Set<Path>): SearchResult {
const dependencyResolver = new DependencyResolver(
this._hasteContext.resolver,
this._hasteContext.moduleMap,
);
return {
paths: dependencyResolver.resolveInverse(
allPaths,
this.isTestFilePath.bind(this),
{
skipNodeResolution: this._options.skipNodeResolution,
},
),
};
}

findOnlyChangedTestPaths(): Promise<SearchResult> {
findChangedTests(): Promise<SearchResult> {
return Promise.all(this._config.testPathDirs.map(determineSCM))
.then(repos => {
if (!repos.every(result => result[0] || result[1])) {
Expand Down Expand Up @@ -234,9 +234,11 @@ class SearchSource {

getTestPaths(patternInfo: PatternInfo): Promise<SearchResult> {
if (patternInfo.onlyChanged) {
return this.findOnlyChangedTestPaths();
return this.findChangedTests();
} else if (patternInfo.testPathPattern != null) {
return this.findMatchingTests(patternInfo.testPathPattern);
return Promise.resolve(
this.findMatchingTests(patternInfo.testPathPattern),
);
} else {
return Promise.resolve({paths: []});
}
Expand Down
66 changes: 30 additions & 36 deletions packages/jest-cli/src/TestRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import type {
TestResult,
} from 'types/TestResult';
import type {Config, Path} from 'types/Config';
import type {HasteResolverContext} from 'types/Runtime';
import type {HasteContext} from 'types/HasteMap';
import type BaseReporter from './reporters/BaseReporter';

const Test = require('./Test');

const fs = require('graceful-fs');
const getCacheFilePath = require('jest-haste-map').getCacheFilePath;
const CoverageReporter = require('./reporters/CoverageReporter');
Expand All @@ -27,7 +29,6 @@ const SummaryReporter = require('./reporters/SummaryReporter');
const VerboseReporter = require('./reporters/VerboseReporter');
const promisify = require('./lib/promisify');
const snapshot = require('jest-snapshot');
const Test = require('./Test');
const workerFarm = require('worker-farm');

type Options = {
Expand All @@ -47,18 +48,18 @@ type OnTestResult = (
const TEST_WORKER_PATH = require.resolve('./TestWorker');

class TestRunner {
_hasteMap: Promise<HasteResolverContext>;
_hasteContext: HasteContext;
_config: Config;
_options: Options;
_dispatcher: ReporterDispatcher;
_testPerformanceCache: Object | null;

constructor(
hasteMap: Promise<HasteResolverContext>,
hasteMap: HasteContext,
config: Config,
options: Options,
) {
this._hasteMap = hasteMap;
this._hasteContext = hasteMap;
this._config = config;
this._options = options;
this._dispatcher = this._setupReporters();
Expand Down Expand Up @@ -153,11 +154,7 @@ class TestRunner {
aggregatedResults.success =
aggregatedResults.numFailedTests === 0 &&
aggregatedResults.numRuntimeErrorTestSuites === 0;
return this._hasteMap
.then(hasteMap => snapshot.cleanup(
hasteMap.instance,
config.updateSnapshot,
))
return snapshot.cleanup(this._hasteContext, config.updateSnapshot)
.then(status => {
aggregatedResults.snapshotFilesRemoved = status.filesRemoved;
aggregatedResults.didUpdate = config.updateSnapshot;
Expand Down Expand Up @@ -188,7 +185,7 @@ class TestRunner {
) {
return testPaths.reduce((promise, path) =>
promise
.then(() => this._hasteMap)
.then(() => this._hasteContext)
.then(data => new Test(path, this._config, data.resolver).run())
.then(result => onTestResult(path, result))
.catch(err => onRunFailure(path, err)),
Expand All @@ -202,31 +199,28 @@ class TestRunner {
onRunFailure: OnRunFailure,
) {
const config = this._config;
return this._hasteMap
.then(() => {
const farm = workerFarm({
autoStart: true,
maxConcurrentCallsPerWorker: 1,
maxRetries: 2, // Allow for a couple of transient errors.
maxConcurrentWorkers: this._options.maxWorkers,
}, TEST_WORKER_PATH);
const runTest = promisify(farm);
return Promise.all(testPaths.map(
path => runTest({path, config})
.then(testResult => onTestResult(path, testResult))
.catch(err => {
onRunFailure(path, err);
if (err.type === 'ProcessTerminatedError') {
console.error(
'A worker process has quit unexpectedly! ' +
'Most likely this an initialization error.',
);
process.exit(1);
}
})),
)
.then(() => workerFarm.end(farm));
});
const farm = workerFarm({
autoStart: true,
maxConcurrentCallsPerWorker: 1,
maxRetries: 2, // Allow for a couple of transient errors.
maxConcurrentWorkers: this._options.maxWorkers,
}, TEST_WORKER_PATH);
const runTest = promisify(farm);
return Promise.all(testPaths.map(
path => runTest({path, config})
.then(testResult => onTestResult(path, testResult))
.catch(err => {
onRunFailure(path, err);
if (err.type === 'ProcessTerminatedError') {
console.error(
'A worker process has quit unexpectedly! ' +
'Most likely this an initialization error.',
);
process.exit(1);
}
})),
)
.then(() => workerFarm.end(farm));
}

_setupReporters(): ReporterDispatcher {
Expand Down
Loading

0 comments on commit 4cf8713

Please sign in to comment.