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

Co-authored-by: GP4cK <gautier.bayzelon@gmail.com>
This commit is contained in:
Tom Mrazauskas 2023-03-04 12:03:50 +02:00 committed by GitHub
parent 679b5adc1f
commit 0060728fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 155 additions and 2 deletions

View File

@ -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))

View File

@ -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
-------------|---------|----------|---------|---------|-------------------"
`;

View File

@ -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();
});

View File

@ -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);
});

View File

@ -0,0 +1,9 @@
{
"jest": {
"rootDir": "./",
"testEnvironment": "node",
"collectCoverageFrom": [
"package/**/*.js"
]
}
}

View File

@ -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));
}

View File

@ -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);

View File

@ -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);
};
@ -99,6 +101,24 @@ 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', () => {
@ -106,11 +126,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(false);
};
@ -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'],
);
});
});
});

View File

@ -34,6 +34,7 @@ export default function shouldInstrument(
filename: string,
options: ShouldInstrumentOptions,
config: Config.ProjectConfig,
loadedFilenames?: Array<string>,
): boolean {
if (!options.collectCoverage) {
return false;
@ -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 &&