Skip to content

Commit

Permalink
feat: add support for v8 code coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Aug 22, 2019
1 parent 7b8393e commit 67cc358
Show file tree
Hide file tree
Showing 30 changed files with 361 additions and 39 deletions.
1 change: 1 addition & 0 deletions TestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = {
testTimeout: 5000,
updateSnapshot: 'none',
useStderr: false,
v8Coverage: false,
verbose: false,
watch: false,
watchAll: false,
Expand Down
1 change: 1 addition & 0 deletions e2e/__tests__/__snapshots__/showConfig.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
"testSequencer": "<<REPLACED_JEST_PACKAGES_DIR>>/jest-test-sequencer/build/index.js",
"updateSnapshot": "all",
"useStderr": false,
"v8Coverage": false,
"verbose": null,
"watch": false,
"watchman": true
Expand Down
21 changes: 21 additions & 0 deletions e2e/__tests__/v8Coverage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import path from 'path';
import runJest from '../runJest';

const DIR = path.resolve(__dirname, '../v8-coverage');

test('does not explode on missing sourcemap', () => {
const sourcemapDir = path.join(DIR, 'no-sourcemap');
const {stderr, status} = runJest(sourcemapDir, ['--v8-coverage'], {
stripAnsi: true,
});

expect(stderr).not.toContain('no such file or directory');
expect(status).toBe(0);
});
3 changes: 3 additions & 0 deletions e2e/v8-coverage/no-sourcemap/Thing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require('./x.css');

module.exports = 42;
4 changes: 4 additions & 0 deletions e2e/v8-coverage/no-sourcemap/__tests__/Thing.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const Thing = require('../Thing');

console.log(Thing);
test.todo('whatever');
4 changes: 4 additions & 0 deletions e2e/v8-coverage/no-sourcemap/cssTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
getCacheKey: () => 'cssTransform',
process: () => 'module.exports = {};',
};
10 changes: 10 additions & 0 deletions e2e/v8-coverage/no-sourcemap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "no-sourcemap",
"version": "1.0.0",
"jest": {
"transform": {
"^.+\\.[jt]sx?$": "babel-jest",
"^.+\\.css$": "<rootDir>/cssTransform.js"
}
}
}
Empty file.
12 changes: 12 additions & 0 deletions packages/jest-cli/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export const check = (argv: Config.Argv) => {
);
}

if (argv.collectCoverage && argv.v8Coverage) {
throw new Error(
'Both --coverage and --v8Coverage were specified, but these two ' +
'options do not make sense together. Which is it?',
);
}

for (const key of [
'onlyChanged',
'lastCommit',
Expand Down Expand Up @@ -681,6 +688,11 @@ export const options = {
'Display individual test results with the test suite hierarchy.',
type: 'boolean' as 'boolean',
},
v8Coverage: {
default: false,
description: 'Collect coverage using V8 instrumentation',
type: 'boolean' as 'boolean',
},
version: {
alias: 'v',
default: undefined,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const defaultOptions: Config.DefaultOptions = {
transform: null,
transformIgnorePatterns: [NODE_MODULES_REGEXP],
useStderr: false,
v8Coverage: false,
verbose: null,
watch: false,
watchPathIgnorePatterns: [],
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const initialOptions: Config.InitialOptions = {
unmockedModulePathPatterns: ['mock'],
updateSnapshot: true,
useStderr: false,
v8Coverage: false,
verbose: false,
watch: false,
watchPathIgnorePatterns: ['<rootDir>/e2e/'],
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const groupOptions = (
testTimeout: options.testTimeout,
updateSnapshot: options.updateSnapshot,
useStderr: options.useStderr,
v8Coverage: options.v8Coverage,
verbose: options.verbose,
watch: options.watch,
watchAll: options.watchAll,
Expand Down
13 changes: 12 additions & 1 deletion packages/jest-config/src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ export default function normalize(
case 'timers':
case 'useStderr':
case 'verbose':
case 'v8Coverage':
case 'watch':
case 'watchAll':
case 'watchman':
Expand Down Expand Up @@ -957,7 +958,10 @@ export default function normalize(
// Is transformed to: `--findRelatedTests '/rootDir/file1.js' --coverage --collectCoverageFrom 'file1.js'`
// where arguments to `--collectCoverageFrom` should be globs (or relative
// paths to the rootDir)
if (newOptions.collectCoverage && argv.findRelatedTests) {
if (
(newOptions.collectCoverage || newOptions.v8Coverage) &&
argv.findRelatedTests
) {
let collectCoverageFrom = argv._.map(filename => {
filename = replaceRootDirInPath(options.rootDir, filename);
return path.isAbsolute(filename)
Expand All @@ -983,6 +987,13 @@ export default function normalize(
newOptions.collectCoverageFrom = collectCoverageFrom;
}

if (newOptions.collectCoverage && newOptions.v8Coverage) {
throw createConfigError(
` Configuration options ${chalk.bold('collectCoverage')} and` +
` ${chalk.bold('v8Coverage')} cannot be used together.`,
);
}

return {
hasDeprecationWarnings,
options: newOptions,
Expand Down
7 changes: 4 additions & 3 deletions packages/jest-core/src/TestScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,15 @@ export default class TestScheduler {
}

private _setupReporters() {
const {collectCoverage, notify, reporters} = this._globalConfig;
const {collectCoverage, notify, reporters, v8Coverage} = this._globalConfig;
const isDefault = this._shouldAddDefaultReporters(reporters);
const willCollectCoverage = collectCoverage || v8Coverage;

if (isDefault) {
this._setupDefaultReporters(collectCoverage);
this._setupDefaultReporters(willCollectCoverage);
}

if (!isDefault && collectCoverage) {
if (!isDefault && willCollectCoverage) {
this.addReporter(
new CoverageReporter(this._globalConfig, {
changedFiles: this._context && this._context.changedFiles,
Expand Down
3 changes: 3 additions & 0 deletions packages/jest-coverage/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/__mocks__/**
**/__tests__/**
src
22 changes: 22 additions & 0 deletions packages/jest-coverage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@jest/coverage",
"version": "24.9.0",
"repository": {
"type": "git",
"url": "https://github.com/facebook/jest.git",
"directory": "packages/jest-coverage"
},
"license": "MIT",
"main": "build/index.js",
"types": "build/index.d.ts",
"devDependencies": {
"@types/node": "*"
},
"engines": {
"node": ">= 8"
},
"publishConfig": {
"access": "public"
},
"gitHead": "0efb1d7809cb96ae87a7601e7802f1dab3774280"
}
76 changes: 76 additions & 0 deletions packages/jest-coverage/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {Profiler, Session} from 'inspector';

export type V8Coverage = ReadonlyArray<Profiler.ScriptCoverage>;

export default class CoverageInstrumenter {
private readonly session = new Session();

async startInstrumenting() {
this.session.connect();

await new Promise((resolve, reject) => {
this.session.post('Profiler.enable', err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});

await new Promise((resolve, reject) => {
this.session.post(
'Profiler.startPreciseCoverage',
{callCount: true, detailed: true},
err => {
if (err) {
reject(err);
} else {
resolve();
}
},
);
});
}

async stopInstrumenting(): Promise<V8Coverage> {
const preciseCoverage = await new Promise<V8Coverage>((resolve, reject) => {
this.session.post('Profiler.takePreciseCoverage', (err, res) => {
if (err) {
reject(err);
} else {
resolve(res.result);
}
});
});

await new Promise((resolve, reject) => {
this.session.post('Profiler.stopPreciseCoverage', err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});

await new Promise((resolve, reject) => {
this.session.post('Profiler.disable', err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});

return preciseCoverage;
}
}
7 changes: 7 additions & 0 deletions packages/jest-coverage/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build"
}
}
39 changes: 22 additions & 17 deletions packages/jest-reporters/src/coverage_reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ export default class CoverageReporter extends BaseReporter {
this._options = options || {};
}

onTestResult(
_test: Test,
testResult: TestResult,
_aggregatedResults: AggregatedResult,
) {
onTestResult(_test: Test, testResult: TestResult) {
if (testResult.coverage) {
this._coverageMap.merge(testResult.coverage);
}

if (this._globalConfig.v8Coverage) {
return;
}

const sourceMaps = testResult.sourceMaps;
if (sourceMaps) {
Object.keys(sourceMaps).forEach(sourcePath => {
Expand Down Expand Up @@ -76,7 +76,7 @@ export default class CoverageReporter extends BaseReporter {
contexts: Set<Context>,
aggregatedResults: AggregatedResult,
) {
await this._addUntestedFiles(this._globalConfig, contexts);
await this._addUntestedFiles(contexts);
const {map, sourceFinder} = this._sourceMapStore.transformCoverage(
this._coverageMap,
);
Expand All @@ -93,9 +93,14 @@ export default class CoverageReporter extends BaseReporter {
}

const tree = istanbulReport.summarizers.pkg(map);
coverageReporters.forEach(reporter => {
tree.visit(istanbulReports.create(reporter, {}), reportContext);
});
try {
coverageReporters.forEach(reporter => {
tree.visit(istanbulReports.create(reporter, {}), reportContext);
});
} catch (e) {
debugger;
throw e;
}
aggregatedResults.coverageMap = map;
} catch (e) {
console.error(
Expand All @@ -110,20 +115,20 @@ export default class CoverageReporter extends BaseReporter {
this._checkThreshold(this._globalConfig, map);
}

private async _addUntestedFiles(
globalConfig: Config.GlobalConfig,
contexts: Set<Context>,
): Promise<void> {
private async _addUntestedFiles(contexts: Set<Context>): Promise<void> {
const files: Array<{config: Config.ProjectConfig; path: string}> = [];

contexts.forEach(context => {
const config = context.config;
if (
globalConfig.collectCoverageFrom &&
globalConfig.collectCoverageFrom.length
this._globalConfig.collectCoverageFrom &&
this._globalConfig.collectCoverageFrom.length
) {
context.hasteFS
.matchFilesWithGlob(globalConfig.collectCoverageFrom, config.rootDir)
.matchFilesWithGlob(
this._globalConfig.collectCoverageFrom,
config.rootDir,
)
.forEach(filePath =>
files.push({
config,
Expand Down Expand Up @@ -163,7 +168,7 @@ export default class CoverageReporter extends BaseReporter {
try {
const result = await worker.worker({
config,
globalConfig,
globalConfig: this._globalConfig,
options: {
...this._options,
changedFiles:
Expand Down
1 change: 1 addition & 0 deletions packages/jest-reporters/src/generateEmptyCoverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function(
collectCoverage: globalConfig.collectCoverage,
collectCoverageFrom: globalConfig.collectCoverageFrom,
collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom,
v8Coverage: globalConfig.v8Coverage,
};
let coverageWorkerResult: CoverageWorkerResult | null = null;
if (shouldInstrument(filename, coverageOptions, config)) {
Expand Down

0 comments on commit 67cc358

Please sign in to comment.