Skip to content

Commit

Permalink
store source maps on disk instead of in worker process memory
Browse files Browse the repository at this point in the history
  • Loading branch information
jwbay committed Feb 13, 2017
1 parent 4b6011f commit ff8941a
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 40 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
bin/
docs/
examples/react-native
packages/jest-runtime/src/__tests__/test_root/hasInlineSourceMap.js
flow-typed/**
packages/*/build/**
types/**
Expand Down
16 changes: 13 additions & 3 deletions packages/jest-cli/src/reporters/CoverageReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const fs = require('fs');
const generateEmptyCoverage = require('../generateEmptyCoverage');
const isCI = require('is-ci');
const istanbulCoverage = require('istanbul-lib-coverage');
const libSourceMaps = require('istanbul-lib-source-maps');

const FAIL_COLOR = chalk.bold.red;
const RUNNING_TEST_COLOR = chalk.bold.dim;
Expand All @@ -30,10 +31,12 @@ const isInteractive = process.stdout.isTTY && !isCI;

class CoverageReporter extends BaseReporter {
_coverageMap: CoverageMap;
_sourceMapStore: any;

constructor() {
super();
this._coverageMap = istanbulCoverage.createCoverageMap({});
this._sourceMapStore = libSourceMaps.createSourceMapStore();
}

onTestResult(
Expand All @@ -42,6 +45,15 @@ class CoverageReporter extends BaseReporter {
aggregatedResults: AggregatedResult,
) {
if (testResult.coverage) {
if (config.mapCoverage) {
Object.keys(testResult.coverage).map(path => {
// $FlowFixMe - ignores null check above
const {inputSourceMapPath} = testResult.coverage[path];
if (inputSourceMapPath) {
this._sourceMapStore.registerURL(path, inputSourceMapPath);
}
});
}
this._coverageMap.merge(testResult.coverage);
// Remove coverage data to free up some memory.
delete testResult.coverage;
Expand All @@ -57,9 +69,7 @@ class CoverageReporter extends BaseReporter {
let map = this._coverageMap;
let sourceFinder: Object;
if (config.mapCoverage) {
const libSourceMaps = require('istanbul-lib-source-maps');
const sourceMapStore = libSourceMaps.createSourceMapStore();
({map, sourceFinder} = sourceMapStore.transformCoverage(map));
({map, sourceFinder} = this._sourceMapStore.transformCoverage(map));
}

const reporter = createReporter();
Expand Down
1 change: 1 addition & 0 deletions packages/jest-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"babel-jest": "^18.0.0",
"babel-plugin-istanbul": "^3.1.2",
"chalk": "^1.1.3",
"convert-source-map": "^1.3.0",
"graceful-fs": "^4.1.6",
"istanbul-lib-instrument": "^1.3.0",
"jest-config": "^18.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ exports[`transform does not instrument with source map if mapCoverage config opt
}});"
`;

exports[`transform instruments with source map if preprocessor inlines it 1`] = `
"({\\"Object.<anonymous>\\":function(module,exports,require,__dirname,__filename,global,jest){/* istanbul ignore next */var cov_25u22311x4 = function () {var path = \\"/fruits/banana.js\\",hash = \\"5dc8e485e00fed337d9101d8fac0e5dd5a88b834\\",global = new Function('return this')(),gcv = \\"__coverage__\\",coverageData = { path: \\"/fruits/banana.js\\", statementMap: { \\"0\\": { start: { line: 1, column: 12 }, end: { line: 1, column: 24 } } }, fnMap: {}, branchMap: { \\"0\\": { loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 24 } }, type: \\"cond-expr\\", locations: [{ start: { line: 1, column: 19 }, end: { line: 1, column: 20 } }, { start: { line: 1, column: 23 }, end: { line: 1, column: 24 } }] } }, s: { \\"0\\": 0 }, f: {}, b: { \\"0\\": [0, 0] }, _coverageSchema: \\"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c\\" },coverage = global[gcv] || (global[gcv] = {});if (coverage[path] && coverage[path].hash === hash) {return coverage[path];}coverageData.hash = hash;return coverage[path] = coverageData;}();var value = /* istanbul ignore next */(++cov_25u22311x4.s[0], true ? /* istanbul ignore next */(++cov_25u22311x4.b[0][0], 1) : /* istanbul ignore next */(++cov_25u22311x4.b[0][1], 0));
;global.__coverage__['/fruits/banana.js'].inputSourceMapPath = '/cache/jest-transform-cache-test/ab/banana_ab.map';
}});"
`;

exports[`transform instruments with source map if preprocessor supplies it 1`] = `
"({\\"Object.<anonymous>\\":function(module,exports,require,__dirname,__filename,global,jest){/* istanbul ignore next */var cov_25u22311x4 = function () {var path = \\"/fruits/banana.js\\",hash = \\"2af15ff13eaa7e17fc0a001b1b75722b8fbeeb74\\",global = new Function('return this')(),gcv = \\"__coverage__\\",coverageData = { path: \\"/fruits/banana.js\\", statementMap: { \\"0\\": { start: { line: 1, column: 0 }, end: { line: 1, column: 7 } } }, fnMap: {}, branchMap: {}, s: { \\"0\\": 0 }, f: {}, b: {}, inputSourceMap: { mappings: \\";AAAA\\", version: 3 }, _coverageSchema: \\"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c\\" },coverage = global[gcv] || (global[gcv] = {});if (coverage[path] && coverage[path].hash === hash) {return coverage[path];}coverageData.hash = hash;return coverage[path] = coverageData;}();++cov_25u22311x4.s[0];content;
"({\\"Object.<anonymous>\\":function(module,exports,require,__dirname,__filename,global,jest){/* istanbul ignore next */var cov_25u22311x4 = function () {var path = \\"/fruits/banana.js\\",hash = \\"4a81f91764b586e5729481ba491440a08d658002\\",global = new Function('return this')(),gcv = \\"__coverage__\\",coverageData = { path: \\"/fruits/banana.js\\", statementMap: { \\"0\\": { start: { line: 1, column: 0 }, end: { line: 1, column: 7 } } }, fnMap: {}, branchMap: {}, s: { \\"0\\": 0 }, f: {}, b: {}, _coverageSchema: \\"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c\\" },coverage = global[gcv] || (global[gcv] = {});if (coverage[path] && coverage[path].hash === hash) {return coverage[path];}coverageData.hash = hash;return coverage[path] = coverageData;}();++cov_25u22311x4.s[0];content;
;global.__coverage__['/fruits/banana.js'].inputSourceMapPath = '/cache/jest-transform-cache-test/ab/banana_ab.map';
}});"
`;

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 57 additions & 9 deletions packages/jest-runtime/src/__tests__/transform-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@
'use strict';

const skipOnWindows = require('skipOnWindows');
const path = require('path');

jest
.mock('graceful-fs')
.mock('jest-file-exists')
.mock('jest-haste-map', () => ({
getCacheFilePath: (cacheDir, baseDir, version) => cacheDir + baseDir,
}))
.mock('jest-util', () => {
const util = require.requireActual('jest-util');
util.createDirectory = jest.fn();
Expand Down Expand Up @@ -47,17 +51,11 @@ jest.mock(
jest.mock(
'preprocessor-with-sourcemaps',
() => {
const getProcessResult = jest.fn();
return {
getCacheKey: jest.fn((content, filename, configStr) => 'ab'),
process: (content, filename, config) => {
return {
content: 'content',
sourceMap: {
mappings: ';AAAA',
version: 3,
},
};
},
getProcessResult,
process: (content, filename, config) => getProcessResult(),
};
},
{virtual: true},
Expand Down Expand Up @@ -238,8 +236,48 @@ describe('transform', () => {
transform: [['^.+\\.js$', 'preprocessor-with-sourcemaps']],
});

const sourceMap = {
mappings: ';AAAA',
version: 3,
};

require('preprocessor-with-sourcemaps').getProcessResult.mockReturnValue({
content: 'content',
sourceMap,
});

transform('/fruits/banana.js', config);
expect(vm.Script.mock.calls[0][0]).toMatchSnapshot();
expect(fs.writeFileSync).toBeCalledWith(
'/cache/jest-transform-cache-test/ab/banana_ab.map',
JSON.stringify(sourceMap),
'utf8',
);
});

it('instruments with source map if preprocessor inlines it', () => {
const realFS = require('fs');

config = Object.assign(config, {
collectCoverage: true,
mapCoverage: true,
transform: [['^.+\\.js$', 'preprocessor-with-sourcemaps']],
});

require('preprocessor-with-sourcemaps').getProcessResult.mockReturnValue({
content: realFS.readFileSync(
path.resolve(__dirname, './test_root/hasInlineSourceMap.js'),
'utf8'
),
});

transform('/fruits/banana.js', config);
expect(vm.Script.mock.calls[0][0]).toMatchSnapshot();
expect(fs.writeFileSync).toBeCalledWith(
'/cache/jest-transform-cache-test/ab/banana_ab.map',
expect.stringMatching(/mappings/),
'utf8',
);
});

it('does not instrument with source map if mapCoverage config option is false', () => {
Expand All @@ -249,6 +287,16 @@ describe('transform', () => {
transform: [['^.+\\.js$', 'preprocessor-with-sourcemaps']],
});

const sourceMap = {
mappings: ';AAAA',
version: 3,
};

require('preprocessor-with-sourcemaps').getProcessResult.mockReturnValue({
content: 'content',
sourceMap,
});

transform('/fruits/banana.js', config);
expect(vm.Script.mock.calls[0][0]).toMatchSnapshot();
});
Expand Down
45 changes: 31 additions & 14 deletions packages/jest-runtime/src/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,38 +235,36 @@ const stripShebang = content => {
};

const instrumentFile = (
transformedSource: TransformedSource,
content: string,
filename: Path,
config: Config,
): string => {
// NOTE: Keeping these requires inside this function reduces a single run
// time by 2sec if not running in `--coverage` mode
const babel = require('babel-core');
const babelPluginIstanbul = require('babel-plugin-istanbul').default;
const pluginOptions: any = {
cwd: config.rootDir, // files outside `cwd` will not be instrumented
exclude: [],
useInlineSourceMaps: config.mapCoverage,
};

if (transformedSource.sourceMap && config.mapCoverage) {
pluginOptions.inputSourceMap = transformedSource.sourceMap;
}

return babel.transform(transformedSource.content, {
return babel.transform(content, {
auxiliaryCommentBefore: ' istanbul ignore next ',
babelrc: false,
filename,
plugins: [
[
babelPluginIstanbul,
pluginOptions,
{
cwd: config.rootDir, // files outside `cwd` will not be instrumented
exclude: [],
useInlineSourceMaps: false,
},
],
],
retainLines: true,
}).code;
};

const escapePathForJavaScript = (filePath: string) =>
filePath.replace(/\\/g, '\\\\');

const transformSource = (
filename: Path,
config: Config,
Expand Down Expand Up @@ -300,7 +298,15 @@ const transformSource = (
}
}

if (!config.mapCoverage) {
if (config.mapCoverage) {
if (!transformed.sourceMap) {
const convert = require('convert-source-map');
const inlineSourceMap = convert.fromSource(transformed.content);
if (inlineSourceMap) {
transformed.sourceMap = inlineSourceMap.toJSON();
}
}
} else {
transformed.sourceMap = null;
}

Expand All @@ -309,11 +315,22 @@ const transformSource = (
const transformDidInstrument = transform && transform.canInstrument;

if (!transformDidInstrument && instrument) {
result = instrumentFile(transformed, filename, config);
result = instrumentFile(transformed.content, filename, config);
} else {
result = transformed.content;
}

if (instrument && transformed.sourceMap && config.mapCoverage) {
const sourceMapContent = typeof transformed.sourceMap === 'string'
? transformed.sourceMap
: JSON.stringify(transformed.sourceMap);
const sourceMapFilePath = cacheFilePath + '.map';
writeCacheFile(sourceMapFilePath, sourceMapContent);
result +=
`\n;global.__coverage__['${escapePathForJavaScript(filename)}']` +
`.inputSourceMapPath = '${escapePathForJavaScript(sourceMapFilePath)}';`;
}

writeCacheFile(cacheFilePath, result);
return result;
};
Expand Down
4 changes: 2 additions & 2 deletions types/TestResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
'use strict';

import type {ConsoleBuffer} from './Console';
import type {SourceMap} from './Transform';

export type RawFileCoverage = {|
path: string,
Expand All @@ -21,7 +20,8 @@ export type RawFileCoverage = {|
fnMap: { [functionId: number]: any },
statementMap: { [statementId: number]: any },
branchMap: { [branchId: number]: any },
inputSourceMap?: SourceMap,
inputSourceMap?: Object,
inputSourceMapPath?: string,
|};

export type RawCoverage = {
Expand Down
12 changes: 1 addition & 11 deletions types/Transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,7 @@ import type {Config, Path} from 'types/Config';

export type TransformedSource = {|
content: string,
sourceMap: ?SourceMap,
|};

export type SourceMap = {|
file: string,
mappings: string,
names: string[],
sourceRoot: string,
sources: string[],
sourcesContent: string[],
version: number,
sourceMap: ?Object | string,
|};

export type TransformOptions = {|
Expand Down

0 comments on commit ff8941a

Please sign in to comment.