diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e81b1b5385..6e4ded5df4f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `[@jest/expect-utils]` Fix deep equality of ImmutableJS OrderedMaps ([#12763](https://github.com/facebook/jest/pull/12899)) - `[jest-docblock]` Handle multiline comments in parseWithComments ([#12845](https://github.com/facebook/jest/pull/12845)) - `[jest-mock]` Improve `spyOn` error messages ([#12901](https://github.com/facebook/jest/pull/12901)) +- `[jest-runtime]` Correctly report V8 coverage with `resetModules: true` ([#12912](https://github.com/facebook/jest/pull/12912)) - `[jest-worker]` Make `JestWorkerFarm` helper type to include methods of worker module that take more than one argument ([#12839](https://github.com/facebook/jest/pull/12839)) ### Chore & Maintenance diff --git a/e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap b/e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap index 58e81f474d72..01767d172899 100644 --- a/e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap +++ b/e2e/__tests__/__snapshots__/coverageProviderV8.test.ts.snap @@ -85,3 +85,23 @@ All files | 100 | 100 | 100 | 100 | x.css | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|-------------------" `; + +exports[`reports coverage with \`resetModules\` 1`] = ` +" console.log + this will print + + at log (module.js:11:11) + + console.log + this will print + + at log (module.js:11:11) + +--------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------|---------|----------|---------|---------|------------------- +All files | 59.37 | 33.33 | 33.33 | 59.37 | + module.js | 79.16 | 50 | 50 | 79.16 | 14-16,19-20 + uncovered.js | 0 | 0 | 0 | 0 | 1-8 +--------------|---------|----------|---------|---------|-------------------" +`; diff --git a/e2e/__tests__/coverageProviderV8.test.ts b/e2e/__tests__/coverageProviderV8.test.ts index e0aed23d0d10..a11ac7676cf5 100644 --- a/e2e/__tests__/coverageProviderV8.test.ts +++ b/e2e/__tests__/coverageProviderV8.test.ts @@ -37,6 +37,19 @@ test('prints coverage with empty sourcemaps', () => { expect(stdout).toMatchSnapshot(); }); +test('reports coverage with `resetModules`', () => { + const sourcemapDir = path.join(DIR, 'with-resetModules'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'v8'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + test('prints correct coverage report, if a CJS module is put under test without transformation', () => { const sourcemapDir = path.join(DIR, 'cjs-native-without-sourcemap'); diff --git a/e2e/coverage-provider-v8/with-resetModules/__tests__/test.js b/e2e/coverage-provider-v8/with-resetModules/__tests__/test.js new file mode 100644 index 000000000000..abc20b24a8a0 --- /dev/null +++ b/e2e/coverage-provider-v8/with-resetModules/__tests__/test.js @@ -0,0 +1,16 @@ +/** + * 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. + */ + +test('dummy', () => { + const {value} = require('../module'); + expect(value).toBe('abc'); +}); + +test('reset dummy', () => { + const {value} = require('../module'); + expect(value).toBe('abc'); +}); diff --git a/e2e/coverage-provider-v8/with-resetModules/module.js b/e2e/coverage-provider-v8/with-resetModules/module.js new file mode 100644 index 000000000000..2178e49d175f --- /dev/null +++ b/e2e/coverage-provider-v8/with-resetModules/module.js @@ -0,0 +1,24 @@ +/** + * 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. + */ + +const value = 'abc'; + +function covered() { + console.log('this will print'); +} + +function uncovered() { + console.log('this will not'); +} + +if (value !== 'abc') { + uncovered(); +} + +covered(); + +module.exports = {value}; diff --git a/e2e/coverage-provider-v8/with-resetModules/package.json b/e2e/coverage-provider-v8/with-resetModules/package.json new file mode 100644 index 000000000000..c9e1544518a9 --- /dev/null +++ b/e2e/coverage-provider-v8/with-resetModules/package.json @@ -0,0 +1,8 @@ +{ + "jest": { + "collectCoverageFrom": [ + "*.js" + ], + "resetModules": true + } +} diff --git a/e2e/coverage-provider-v8/with-resetModules/uncovered.js b/e2e/coverage-provider-v8/with-resetModules/uncovered.js new file mode 100644 index 000000000000..54e6c946a5f7 --- /dev/null +++ b/e2e/coverage-provider-v8/with-resetModules/uncovered.js @@ -0,0 +1,8 @@ +/** + * 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. + */ + +module.exports = {}; diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 5cceb9bef2b6..b2a08df90b7c 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -221,6 +221,7 @@ export default class Runtime { private readonly _fileTransformsMutex: Map>; private _v8CoverageInstrumenter: CoverageInstrumenter | undefined; private _v8CoverageResult: V8Coverage | undefined; + private _v8CoverageSources: Map | undefined; private readonly _transitiveShouldMock: Map; private _unmockList: RegExp | undefined; private readonly _virtualMocks: Map; @@ -1155,6 +1156,18 @@ export default class Runtime { this._cjsNamedExports.clear(); this._moduleMockRegistry.clear(); this._cacheFS.clear(); + + if ( + this._coverageOptions.collectCoverage && + this._coverageOptions.coverageProvider === 'v8' && + this._v8CoverageSources + ) { + this._v8CoverageSources = new Map([ + ...this._v8CoverageSources, + ...this._fileTransforms, + ]); + } + this._fileTransforms.clear(); if (this._environment) { @@ -1182,16 +1195,21 @@ export default class Runtime { async collectV8Coverage(): Promise { this._v8CoverageInstrumenter = new CoverageInstrumenter(); + this._v8CoverageSources = new Map(); await this._v8CoverageInstrumenter.startInstrumenting(); } async stopCollectingV8Coverage(): Promise { - if (!this._v8CoverageInstrumenter) { + if (!this._v8CoverageInstrumenter || !this._v8CoverageSources) { throw new Error('You need to call `collectV8Coverage` first.'); } this._v8CoverageResult = await this._v8CoverageInstrumenter.stopInstrumenting(); + this._v8CoverageSources = new Map([ + ...this._v8CoverageSources, + ...this._fileTransforms, + ]); } getAllCoverageInfoCopy(): JestEnvironment['global']['__coverage__'] { @@ -1199,8 +1217,8 @@ export default class Runtime { } getAllV8CoverageInfoCopy(): V8CoverageResult { - if (!this._v8CoverageResult) { - throw new Error('You need to `stopCollectingV8Coverage` first'); + if (!this._v8CoverageResult || !this._v8CoverageSources) { + throw new Error('You need to call `stopCollectingV8Coverage` first.'); } return this._v8CoverageResult @@ -1210,11 +1228,11 @@ 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._fileTransforms.has(res.url) && + this._v8CoverageSources!.has(res.url) && shouldInstrument(res.url, this._coverageOptions, this._config), ) .map(result => { - const transformedFile = this._fileTransforms.get(result.url); + const transformedFile = this._v8CoverageSources!.get(result.url); return { codeTransformResult: transformedFile, @@ -1307,6 +1325,7 @@ export default class Runtime { this._fileTransformsMutex.clear(); this.jestObjectCaches.clear(); + this._v8CoverageSources?.clear(); this._v8CoverageResult = []; this._v8CoverageInstrumenter = undefined; this._moduleImplementation = undefined;