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
Expand Up @@ -11,6 +11,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-snapshot]` Add support to `cts` and `mts` TypeScript files to inline snapshots ([#13975](https://github.com/facebook/jest/pull/13975))
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))

Expand Down
9 changes: 9 additions & 0 deletions e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap
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
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
@@ -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
@@ -0,0 +1,9 @@
{
"jest": {
"rootDir": "./",
"testEnvironment": "node",
"collectCoverageFrom": [
"package/**/*.js"
]
}
}
27 changes: 27 additions & 0 deletions e2e/vmscript-coverage/package/vmscript.js
@@ -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
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,
/* loadedFilenames */ Array.from(this._v8CoverageSources!.keys()),
),
)
.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
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
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