Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest-runtime, @jest/transform): allow V8 coverage provider to collect coverage from files which were not loaded explicitly #13974

Merged
merged 13 commits into from
Mar 4, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- `[jest-message-util]` Add support for [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) ([#13946](https://github.com/facebook/jest/pull/13946) & [#13947](https://github.com/facebook/jest/pull/13947))
- `[jest-message-util]` Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in `test` and `it` ([#13935](https://github.com/facebook/jest/pull/13935) & [#13966](https://github.com/facebook/jest/pull/13966))
- `[jest-reporters]` Add `summaryThreshold` option to summary reporter to allow overriding the internal threshold that is used to print the summary of all failed tests when the number of test suites surpasses it ([#13895](https://github.com/facebook/jest/pull/13895))
- `[jest-runtime, @jest/transform]` Allow V8 coverage provider to collect coverage from files which were not loaded explicitly ([#13974](https://github.com/facebook/jest/pull/13974))
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))

### Fixes
Expand Down
9 changes: 9 additions & 0 deletions e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,12 @@ All files | 59.37 | 33.33 | 33.33 | 59.37 |
uncovered.js | 0 | 0 | 0 | 0 | 1-8
--------------|---------|----------|---------|---------|-------------------"
`;

exports[`vm script coverage generator 1`] = `
"-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 88.88 | 100 | 66.66 | 88.88 |
vmscript.js | 88.88 | 100 | 66.66 | 88.88 | 20-22
-------------|---------|----------|---------|---------|-------------------"
`;
12 changes: 12 additions & 0 deletions e2e/__tests__/coverageProviderV8.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,15 @@ test('prints correct coverage report, if a TS module is transpiled by custom tra
expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
});

test('vm script coverage generator', () => {
const dir = path.resolve(__dirname, '../vmscript-coverage');
const {stdout, exitCode} = runJest(
dir,
['--coverage', '--coverage-provider', 'v8'],
{stripAnsi: true},
);

expect(exitCode).toBe(0);
expect(stdout).toMatchSnapshot();
});
42 changes: 42 additions & 0 deletions e2e/vmscript-coverage/__tests__/extract-coverage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const fs = require('fs');
const path = require('path');
const vm = require('vm');
const filePath = path.resolve(__dirname, '../package/vmscript.js');

test('extract coverage', () => {
const content = fs.readFileSync(filePath, {encoding: 'utf8'});

const case1 = vm.runInNewContext(
content,
{
inputObject: {
number: 0,
},
},
{
filename: filePath,
},
);

const case2 = vm.runInNewContext(
content,
{
inputObject: {
number: 7,
},
},
{
filename: filePath,
},
);

expect(case1).toBe(false);
expect(case2).toBe(true);
});
9 changes: 9 additions & 0 deletions e2e/vmscript-coverage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"jest": {
"rootDir": "./",
"testEnvironment": "node",
"collectCoverageFrom": [
"package/**/*.js"
]
}
}
27 changes: 27 additions & 0 deletions e2e/vmscript-coverage/package/vmscript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

function addOne(inputNumber) {
return ++inputNumber;
}

function isEven(inputNumber) {
if (inputNumber % 2 === 0) {
return true;
} else {
return false;
}
}

function notCovered() {
return 'not covered';
}

/* global inputObject */
if (inputObject.number / 1 === inputObject.number) {
isEven(addOne(inputObject.number));
}
8 changes: 6 additions & 2 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1267,8 +1267,12 @@ export default class Runtime {
res =>
// TODO: will this work on windows? It might be better if `shouldInstrument` deals with it anyways
res.url.startsWith(this._config.rootDir) &&
this._v8CoverageSources!.has(res.url) &&
shouldInstrument(res.url, this._coverageOptions, this._config),
shouldInstrument(
res.url,
this._coverageOptions,
this._config,
Array.from(this._v8CoverageSources!.keys()),
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
),
)
.map(result => {
const transformedFile = this._v8CoverageSources!.get(result.url);
Expand Down
40 changes: 40 additions & 0 deletions packages/jest-transform/src/__tests__/shouldInstrument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ describe('shouldInstrument', () => {
filename = defaultFilename,
options: Partial<Options>,
config: Partial<Config.ProjectConfig>,
loadedFilenames?: Array<string>,
) => {
const result = shouldInstrument(
filename,
{...defaultOptions, ...options},
{...defaultConfig, ...config},
loadedFilenames,
);
expect(result).toBe(true);
};
Expand Down Expand Up @@ -99,18 +101,38 @@ describe('shouldInstrument', () => {
testRegex: ['.*\\.(test)\\.(js)$'],
});
});

it('when file is in loadedFilenames list', () => {
testShouldInstrument(
'do/collect/coverage.js',
defaultOptions,
defaultConfig,
['do/collect/coverage.js'],
);
});

it('when file is in not loadedFilenames list, but matches collectCoverageFrom', () => {
testShouldInstrument(
'do/collect/coverage.js',
{collectCoverageFrom: ['!**/dont/**/*.js', '**/do/**/*.js']},
defaultConfig,
['dont/collect/coverage.js'],
);
});
});

describe('should return false', () => {
const testShouldInstrument = (
filename = defaultFilename,
options: Partial<Options>,
config: Partial<Config.ProjectConfig>,
loadedFilenames?: Array<string>,
) => {
const result = shouldInstrument(
filename,
{...defaultOptions, ...options},
{...defaultConfig, ...config},
loadedFilenames,
);
expect(result).toBe(false);
};
Expand Down Expand Up @@ -205,5 +227,23 @@ describe('shouldInstrument', () => {
setupFilesAfterEnv: ['setupTest.js'],
});
});

it('when file is not in loadedFilenames list', () => {
testShouldInstrument(
'dont/collect/coverage.js',
defaultOptions,
defaultConfig,
['do/collect/coverage.js'],
);
});

it('when file is in not loadedFilenames list and does not match collectCoverageFrom', () => {
testShouldInstrument(
'dont/collect/coverage.js',
{collectCoverageFrom: ['!**/dont/**/*.js', '**/do/**/*.js']},
defaultConfig,
['do/collect/coverage.js'],
);
});
});
});
9 changes: 9 additions & 0 deletions packages/jest-transform/src/shouldInstrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function shouldInstrument(
filename: string,
options: ShouldInstrumentOptions,
config: Config.ProjectConfig,
loadedFilenames?: Array<string>,
): boolean {
if (!options.collectCoverage) {
return false;
Expand All @@ -60,6 +61,14 @@ export default function shouldInstrument(
}
}

if (
options.collectCoverageFrom.length === 0 &&
loadedFilenames != null &&
!loadedFilenames.includes(filename)
) {
return false;
}

if (
// still cover if `only` is specified
options.collectCoverageFrom.length &&
Expand Down