From cf49e944f7e20a6fe5cd3158478d1ab2280f699d Mon Sep 17 00:00:00 2001 From: Eric MORAND Date: Wed, 23 Oct 2024 18:21:34 +0200 Subject: [PATCH 1/2] Add One Double Zero as coverage provider --- docs/CLI.md | 2 +- docs/Configuration.md | 30 +++- .../coverageProviderODZ.test.ts.snap | 116 ++++++++++++++++ e2e/__tests__/coverageProviderODZ.test.ts | 120 ++++++++++++++++ .../empty-sourcemap/package.json | 5 +- .../no-sourcemap/package.json | 10 +- packages/jest-cli/src/args.ts | 5 +- packages/jest-reporters/package.json | 1 + .../jest-reporters/src/CoverageReporter.ts | 129 +++++++++++++++++- .../src/generateEmptyCoverage.ts | 5 +- packages/jest-runner/src/runTest.ts | 3 +- packages/jest-schemas/src/raw-types.ts | 1 + packages/jest-types/src/Config.ts | 2 +- 13 files changed, 416 insertions(+), 13 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/coverageProviderODZ.test.ts.snap create mode 100644 e2e/__tests__/coverageProviderODZ.test.ts diff --git a/docs/CLI.md b/docs/CLI.md index 59fcd897806e..0e067d68f701 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -170,7 +170,7 @@ The directory where Jest should output its coverage files. ### `--coverageProvider=` -Indicates which provider should be used to instrument code for coverage. Allowed values are `babel` (default) or `v8`. +Indicates which provider should be used to instrument code for coverage. Allowed values are `babel` (default), `v8` or `odz`. ### `--debug` diff --git a/docs/Configuration.md b/docs/Configuration.md index ac7a5bd01104..940f7c9a7261 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -224,11 +224,35 @@ Default: `false` Indicates whether the coverage information should be collected while executing the test. Because this retrofits all executed files with coverage collection statements, it may significantly slow down your tests. -Jest ships with two coverage providers: `babel` (default) and `v8`. See the [`coverageProvider`](#coverageprovider-string) option for more details. +Jest ships with three coverage providers: `babel` (default), `v8` and `odz`. See the [`coverageProvider`](#coverageprovider-string) option for more details. :::info -The `babel` and `v8` coverage providers use `/* istanbul ignore next */` and `/* c8 ignore next */` comments to exclude lines from coverage reports, respectively. For more information, you can view the [`istanbuljs` documentation](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines) and the [`c8` documentation](https://github.com/bcoe/c8#ignoring-uncovered-lines-functions-and-blocks). +The `babel` and `v8` coverage providers use `/* istanbul ignore next */` and `/* c8 ignore next */` comments to exclude lines from coverage reports, respectively. For more information, you can view the [`istanbuljs` documentation](https://github.com/istanbuljs/nyc#parsing-hints-ignoring-lines) and the [`c8` documentation](https://github.com/bcoe/c8#ignoring-uncovered-lines-functions-and-blocks). The `odz` coverage provider doesn't support exclusion comment. + +The `v8` coverage provider comes with the following tradeoffs: + +- It is not a 1:1 replacement for Babel/Istanbul coverage + + - Switching between the two will usually change the reported coverage statistics, which can change whether coverage thresholds are reached or not + - Switching between the two can cause regions of uncovered code to be discovered or ignored (This is mostly just an overall summary of the other points) + +- It works by taking a coverage report for output/transpiled code, and then using `v8-to-istanbul` and source maps to convert that report into an Istanbul-compatible format + - This is an inherently imprecise and heuristic-driven process, though in many cases it works well enough for practical purposes + - In some cases this can give confusing or misleading results, or fail to distinguish between user code and generated code (e.g. uncovered branches introduced by `__esModule` detection shims) + - Babel/Istanbul is able to be more precise because it usually operates directly on the user's original source code +- It tracks “blocks”, not individual statements + - In particular, if you have a sequence of statements, and the middle statements always throw an exception, V8 coverage will mark the later statements as “covered” even though they never ran + - This is a deliberate tradeoff made by the V8 developers who implemented coverage + - Babel/Istanbul is able to be more precise because it explicitly instruments every source statement +- It does not track the else branch of an if-statement without an explicit else + + - So if a one-sided if-statement's condition is always true, V8 will not warn about an uncovered branch + - Babel/Istanbul is able to track these by artificially inserting an else with a branch counter + +- It does not respect the `collectCoverageFrom` Jest configuration: regardless of the value of `collectCoverageFrom`, it emits coverage report for whatever file that was encountered during the execution of the tests. + +The `odz` coverage provider also makes use of V8 coverage data, but doesn't come any of the `v8` provider tradeoffs because it operates at the AST level. ::: @@ -314,7 +338,7 @@ These pattern strings match against the full path. Use the `` string to ### `coverageProvider` \[string] -Indicates which provider should be used to instrument code for coverage. Allowed values are `babel` (default) or `v8`. +Indicates which provider should be used to instrument code for coverage. Allowed values are `babel` (default), `v8` and `odz`. ### `coverageReporters` \[array<string | \[string, options]>] diff --git a/e2e/__tests__/__snapshots__/coverageProviderODZ.test.ts.snap b/e2e/__tests__/__snapshots__/coverageProviderODZ.test.ts.snap new file mode 100644 index 000000000000..82462c8681b5 --- /dev/null +++ b/e2e/__tests__/__snapshots__/coverageProviderODZ.test.ts.snap @@ -0,0 +1,116 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prints correct coverage report, if a CJS module is put under test without transformation 1`] = ` +" console.log + this will print + + at covered (module.js:11:11) + +--------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------|---------|----------|---------|---------|------------------- +All files | 60 | 50 | 50 | 60 | + module.js | 66.66 | 50 | 50 | 66.66 | 14-15,19 + uncovered.js | 0 | 100 | 100 | 0 | 8 +--------------|---------|----------|---------|---------|-------------------" +`; + +exports[`prints correct coverage report, if a TS module is transpiled by Babel to CJS and put under test 1`] = ` +" console.log + this will print + + at log (module.ts:13:11) + +--------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------|---------|----------|---------|---------|------------------- +All files | 62.5 | 50 | 50 | 62.5 | + module.ts | 62.5 | 50 | 50 | 62.5 | 16-17,21 + types.ts | 0 | 0 | 0 | 0 | + uncovered.ts | 0 | 0 | 0 | 0 | +--------------|---------|----------|---------|---------|-------------------" +`; + +exports[`prints correct coverage report, if a TS module is transpiled by custom transformer to ESM put under test 1`] = ` +" console.log + this will print + + at covered (module.ts:13:11) + +--------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------|---------|----------|---------|---------|------------------- +All files | 62.5 | 50 | 50 | 62.5 | + module.ts | 62.5 | 50 | 50 | 62.5 | 16-17,21 + types.ts | 0 | 0 | 0 | 0 | + uncovered.ts | 0 | 0 | 0 | 0 | +--------------|---------|----------|---------|---------|-------------------" +`; + +exports[`prints correct coverage report, if an ESM module is put under test without transformation 1`] = ` +" console.log + this will print + + at covered (module.js:11:11) + +--------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------|---------|----------|---------|---------|------------------- +All files | 62.5 | 50 | 50 | 62.5 | + module.js | 62.5 | 50 | 50 | 62.5 | 14-15,19 + uncovered.js | 0 | 0 | 0 | 0 | +--------------|---------|----------|---------|---------|-------------------" +`; + +exports[`prints coverage with empty sourcemaps 1`] = ` +"----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 0 | 0 | 0 | 0 | + types.ts | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|-------------------" +`; + +exports[`prints coverage with missing sourcemaps 1`] = ` +" console.log + 42 + + at Object.log (__tests__/Thing.test.js:10:9) + +----------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +----------|---------|----------|---------|---------|------------------- +All files | 100 | 100 | 100 | 100 | + Thing.js | 100 | 100 | 100 | 100 | + x.css | 0 | 0 | 0 | 0 | +----------|---------|----------|---------|---------|-------------------" +`; + +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 | 60 | 50 | 50 | 60 | + module.js | 66.66 | 50 | 50 | 66.66 | 14-15,19 + uncovered.js | 0 | 100 | 100 | 0 | 8 +--------------|---------|----------|---------|---------|-------------------" +`; + +exports[`vm script coverage generator 1`] = ` +"-------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +-------------|---------|----------|---------|---------|------------------- +All files | 80 | 75 | 66.66 | 80 | + vmscript.js | 80 | 75 | 66.66 | 80 | 20-21 +-------------|---------|----------|---------|---------|-------------------" +`; diff --git a/e2e/__tests__/coverageProviderODZ.test.ts b/e2e/__tests__/coverageProviderODZ.test.ts new file mode 100644 index 000000000000..48734078be2c --- /dev/null +++ b/e2e/__tests__/coverageProviderODZ.test.ts @@ -0,0 +1,120 @@ +/** + * 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. + */ + +import * as path from 'path'; +import runJest from '../runJest'; + +const DIR = path.resolve(__dirname, '../coverage-provider-v8'); + +test('prints coverage with missing sourcemaps', () => { + const sourcemapDir = path.join(DIR, 'no-sourcemap'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + +test('prints coverage with empty sourcemaps', () => { + const sourcemapDir = path.join(DIR, 'empty-sourcemap'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + +test('reports coverage with `resetModules`', () => { + const sourcemapDir = path.join(DIR, 'with-resetModules'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz'], + {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'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz', '--no-cache'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + +test('prints correct coverage report, if a TS module is transpiled by Babel to CJS and put under test', () => { + const sourcemapDir = path.join(DIR, 'cjs-with-babel-transformer'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz', '--no-cache'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + +test('prints correct coverage report, if an ESM module is put under test without transformation', () => { + const sourcemapDir = path.join(DIR, 'esm-native-without-sourcemap'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz', '--no-cache'], + { + nodeOptions: '--experimental-vm-modules --no-warnings', + stripAnsi: true, + }, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); + +test('prints correct coverage report, if a TS module is transpiled by custom transformer to ESM put under test', () => { + const sourcemapDir = path.join(DIR, 'esm-with-custom-transformer'); + + const {stdout, exitCode} = runJest( + sourcemapDir, + ['--coverage', '--coverage-provider', 'odz', '--no-cache'], + { + nodeOptions: '--experimental-vm-modules --no-warnings', + stripAnsi: true, + }, + ); + + 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', 'odz'], + {stripAnsi: true}, + ); + + expect(exitCode).toBe(0); + expect(stdout).toMatchSnapshot(); +}); diff --git a/e2e/coverage-provider-v8/empty-sourcemap/package.json b/e2e/coverage-provider-v8/empty-sourcemap/package.json index d3ae283aee02..5bfdc53c3bb4 100644 --- a/e2e/coverage-provider-v8/empty-sourcemap/package.json +++ b/e2e/coverage-provider-v8/empty-sourcemap/package.json @@ -2,6 +2,9 @@ "name": "empty-sourcemap", "version": "1.0.0", "jest": { - "testEnvironment": "node" + "testEnvironment": "node", + "collectCoverageFrom": [ + "/types.ts" + ] } } diff --git a/e2e/coverage-provider-v8/no-sourcemap/package.json b/e2e/coverage-provider-v8/no-sourcemap/package.json index e63e40a94614..e1ed486d1e88 100644 --- a/e2e/coverage-provider-v8/no-sourcemap/package.json +++ b/e2e/coverage-provider-v8/no-sourcemap/package.json @@ -6,6 +6,14 @@ "transform": { "\\.[jt]sx?$": "babel-jest", "\\.css$": "/cssTransform.js" - } + }, + "moduleFileExtensions": [ + "js", + "css" + ], + "collectCoverageFrom": [ + "/Thing.js", + "/x.css" + ] } } diff --git a/packages/jest-cli/src/args.ts b/packages/jest-cli/src/args.ts index f6993afcd784..2cca18082308 100644 --- a/packages/jest-cli/src/args.ts +++ b/packages/jest-cli/src/args.ts @@ -213,8 +213,9 @@ export const options: {[key: string]: Options} = { type: 'array', }, coverageProvider: { - choices: ['babel', 'v8'], - description: 'Select between Babel and V8 to collect coverage', + choices: ['babel', 'v8', 'odz'], + description: + 'Select between Babel, V8 and One Double Zero to collect coverage', requiresArg: true, }, coverageReporters: { diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index 415cf37d6ad7..21e6065d9157 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -34,6 +34,7 @@ "jest-message-util": "workspace:*", "jest-util": "workspace:*", "jest-worker": "workspace:*", + "one-double-zero": "1.0.0-beta.14", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", diff --git a/packages/jest-reporters/src/CoverageReporter.ts b/packages/jest-reporters/src/CoverageReporter.ts index 39b8636e39fa..a976a57383f5 100644 --- a/packages/jest-reporters/src/CoverageReporter.ts +++ b/packages/jest-reporters/src/CoverageReporter.ts @@ -6,6 +6,7 @@ */ import * as path from 'path'; +import {fileURLToPath, pathToFileURL} from 'url'; import {mergeProcessCovs} from '@bcoe/v8-coverage'; import type {EncodedSourceMap} from '@jridgewell/trace-mapping'; import chalk = require('chalk'); @@ -15,6 +16,11 @@ import istanbulCoverage = require('istanbul-lib-coverage'); import istanbulReport = require('istanbul-lib-report'); import libSourceMaps = require('istanbul-lib-source-maps'); import istanbulReports = require('istanbul-reports'); +import { + type ProcessCoverage, + type SourceMap, + createOneDoubleZero, +} from 'one-double-zero'; import v8toIstanbul = require('v8-to-istanbul'); import type { AggregatedResult, @@ -70,7 +76,21 @@ export default class CoverageReporter extends BaseReporter { aggregatedResults: AggregatedResult, ): Promise { await this._addUntestedFiles(testContexts); - const {map, reportContext} = await this._getCoverageResult(); + + const sourceFiles: Array = []; + + for (const testContext of testContexts) { + for (const filePath of testContext.hasteFS.matchFilesWithGlob( + this._globalConfig.collectCoverageFrom, + testContext.config.rootDir, + )) { + if (!sourceFiles.includes(filePath)) { + sourceFiles.push(filePath); + } + } + } + + const {map, reportContext} = await this._getCoverageResult(sourceFiles); try { const coverageReporters = this._globalConfig.coverageReporters || []; @@ -432,7 +452,7 @@ export default class CoverageReporter extends BaseReporter { } } - private async _getCoverageResult(): Promise<{ + private async _getCoverageResult(sourceFiles: Array): Promise<{ map: istanbulCoverage.CoverageMap; reportContext: istanbulReport.Context; }> { @@ -502,6 +522,111 @@ export default class CoverageReporter extends BaseReporter { return {map, reportContext}; } + if (this._globalConfig.coverageProvider === 'odz') { + const mergedCoverages = mergeProcessCovs( + this._v8CoverageResults.map(coverageResult => { + return { + result: coverageResult.map(result => { + const wrapperLength = + result.codeTransformResult?.wrapperLength || 0; + + return { + functions: result.result.functions.map(functionCoverage => { + return { + functionName: functionCoverage.functionName, + isBlockCoverage: functionCoverage.isBlockCoverage, + ranges: functionCoverage.ranges.map(rangeCoverage => { + const startOffset = Math.max( + 0, + rangeCoverage.startOffset - wrapperLength, + ); + const endOffset = Math.max( + 0, + rangeCoverage.endOffset - wrapperLength, + ); + + return { + count: rangeCoverage.count, + endOffset, + startOffset, + }; + }), + }; + }), + scriptId: result.result.scriptId, + url: result.result.url, + }; + }), + }; + }), + ); + + const fileTransforms = new Map(); + + for (const v8CoverageResult of this._v8CoverageResults) { + for (const entry of v8CoverageResult) { + if ( + entry.codeTransformResult && + !fileTransforms.has(entry.result.url) + ) { + fileTransforms.set(entry.result.url, entry.codeTransformResult); + } + } + } + + const loadProcessCoverage = (): ProcessCoverage => { + const sourceMaps = new Map(); + + for (const scriptCoverage of mergedCoverages.result) { + const fileTransform = fileTransforms.get(scriptCoverage.url); + + // the v8 coverage collector is replacing the URLS with paths; we need to restore the original URLs. + scriptCoverage.url = pathToFileURL(scriptCoverage.url).href; + + if ( + fileTransform?.sourceMapPath && + fs.existsSync(fileTransform.sourceMapPath) + ) { + const sourceMapContent = JSON.parse( + fs.readFileSync(fileTransform.sourceMapPath, 'utf8'), + ) as SourceMap; + + const scriptFilePath = fileURLToPath(scriptCoverage.url); + + sourceMaps.set(scriptCoverage.url, { + ...sourceMapContent, + file: scriptCoverage.url, + scriptContent: fileTransform.code, + sourceRoot: pathToFileURL(path.dirname(scriptFilePath)).href, + }); + } + } + + return { + scriptCoverages: mergedCoverages.result, + sourceMaps, + }; + }; + + const oneDoubleZero = createOneDoubleZero(() => { + return; + }, fs.readFileSync); + const processCoverage = loadProcessCoverage(); + + return oneDoubleZero + .getCoverageMap(sourceFiles, processCoverage) + .then(coverageMap => { + return { + map: coverageMap, + reportContext: istanbulReport.createContext({ + coverageMap, + dir: this._globalConfig.coverageDirectory, + watermarks: getWatermarks(this._globalConfig), + }), + }; + }); + } + const map = await this._sourceMapStore.transformCoverage(this._coverageMap); const reportContext = istanbulReport.createContext({ coverageMap: map, diff --git a/packages/jest-reporters/src/generateEmptyCoverage.ts b/packages/jest-reporters/src/generateEmptyCoverage.ts index 2c96be14c2bf..d6fc200d1747 100644 --- a/packages/jest-reporters/src/generateEmptyCoverage.ts +++ b/packages/jest-reporters/src/generateEmptyCoverage.ts @@ -41,7 +41,10 @@ export default async function generateEmptyCoverage( }; let coverageWorkerResult: CoverageWorkerResult | null = null; if (shouldInstrument(filename, coverageOptions, config)) { - if (coverageOptions.coverageProvider === 'v8') { + if ( + coverageOptions.coverageProvider === 'v8' || + coverageOptions.coverageProvider === 'odz' + ) { const stat = fs.statSync(filename); return { kind: 'V8Coverage', diff --git a/packages/jest-runner/src/runTest.ts b/packages/jest-runner/src/runTest.ts index 5a20296dd21e..cdbb4a77f328 100644 --- a/packages/jest-runner/src/runTest.ts +++ b/packages/jest-runner/src/runTest.ts @@ -297,7 +297,8 @@ async function runTestInternal( // if we don't have `getVmContext` on the env skip coverage const collectV8Coverage = globalConfig.collectCoverage && - globalConfig.coverageProvider === 'v8' && + (globalConfig.coverageProvider === 'v8' || + globalConfig.coverageProvider === 'odz') && typeof environment.getVmContext === 'function'; // Node's error-message stack size is limited at 10, but it's pretty useful diff --git a/packages/jest-schemas/src/raw-types.ts b/packages/jest-schemas/src/raw-types.ts index 34b9af7a873d..ac9e358af09b 100644 --- a/packages/jest-schemas/src/raw-types.ts +++ b/packages/jest-schemas/src/raw-types.ts @@ -37,6 +37,7 @@ export const SnapshotFormat = Type.Partial( const CoverageProvider = Type.Union([ Type.Literal('babel'), Type.Literal('v8'), + Type.Literal('odz'), ]); const CoverageThresholdValue = Type.Partial( diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 154e4bfc1043..e1bdb41ac77a 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -13,7 +13,7 @@ import type {InitialOptions, SnapshotFormat} from '@jest/schemas'; export type {InitialOptions} from '@jest/schemas'; -type CoverageProvider = 'babel' | 'v8'; +type CoverageProvider = 'babel' | 'v8' | 'odz'; export type FakeableAPI = | 'Date' From 278a534ceb38883e47d8497d105e4912be11bd7f Mon Sep 17 00:00:00 2001 From: Eric MORAND Date: Wed, 22 Jan 2025 18:26:07 +0100 Subject: [PATCH 2/2] Replace one-double-zero with one-double-zero-core one-double-zero-core embarks its own lightweight parser, instead of depending on the gigantic TypeScript package. --- packages/jest-reporters/package.json | 2 +- .../jest-reporters/src/CoverageReporter.ts | 2 +- .../src/__tests__/CoverageReporter.test.js | 61 +++++++++++++++++++ yarn.lock | 13 +++- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index 21e6065d9157..a63d683a4473 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -34,7 +34,7 @@ "jest-message-util": "workspace:*", "jest-util": "workspace:*", "jest-worker": "workspace:*", - "one-double-zero": "1.0.0-beta.14", + "one-double-zero-core": "^1.0.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", diff --git a/packages/jest-reporters/src/CoverageReporter.ts b/packages/jest-reporters/src/CoverageReporter.ts index a976a57383f5..28b4c8b6f607 100644 --- a/packages/jest-reporters/src/CoverageReporter.ts +++ b/packages/jest-reporters/src/CoverageReporter.ts @@ -20,7 +20,7 @@ import { type ProcessCoverage, type SourceMap, createOneDoubleZero, -} from 'one-double-zero'; +} from 'one-double-zero-core'; import v8toIstanbul = require('v8-to-istanbul'); import type { AggregatedResult, diff --git a/packages/jest-reporters/src/__tests__/CoverageReporter.test.js b/packages/jest-reporters/src/__tests__/CoverageReporter.test.js index 59965635bf0b..05a6d955ccce 100644 --- a/packages/jest-reporters/src/__tests__/CoverageReporter.test.js +++ b/packages/jest-reporters/src/__tests__/CoverageReporter.test.js @@ -43,6 +43,7 @@ beforeEach(() => { }, 'non_covered_file.js': '', 'relative_path_file.js': '', + 'statement.js': 'const foo = 5;', }; fileTree[`${process.cwd()}/path-test`] = { '100pc_coverage_file.js': '', @@ -448,4 +449,64 @@ describe('onRunComplete', () => { expect(testReporter.getLastError()).toBeUndefined(); }); }); + + test('getLastError() returns an error when threshold is not met for global (odz)', () => { + const testReporter = new CoverageReporter({ + collectCoverage: true, + coverageProvider: 'odz', + coverageThreshold: { + global: { + statements: 100, + }, + }, + }); + /** + * @type {Array} + */ + const v8CoverageResults = [ + [ + { + codeTransformResult: { + code: 'const foo = 5;', + originalCode: 'const foo = 5;', + sourceMapPath: '', + wrapperLength: 0, + }, + result: { + functions: [ + { + functionName: '', + isBlockCoverage: true, + ranges: [ + { + count: 0, + endOffset: 14, + startOffset: 0, + }, + ], + }, + ], + url: 'path-test-files/statement.js', + }, + }, + ], + ]; + + testReporter._v8CoverageResults = v8CoverageResults; + testReporter.log = jest.fn(); + + const testContext = { + config: { + rootDir: '.', + }, + hasteFS: { + matchFilesWithGlob: () => ['path-test-files/statement.js'], + }, + }; + return testReporter + .onRunComplete(new Set([testContext]), {}, mockAggResults) + .then(() => { + expect(testReporter.getLastError().message.split('\n')).toHaveLength(1); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 236b33c54c21..e75bf464728e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3862,6 +3862,7 @@ __metadata: jest-worker: "workspace:*" mock-fs: ^5.1.2 node-notifier: ^10.0.0 + one-double-zero-core: ^1.0.0 slash: ^3.0.0 string-length: ^4.0.1 strip-ansi: ^6.0.0 @@ -13389,7 +13390,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 2367407a8d13982d8f7a859a35e7f8dd5d8f75aae4bb5484ede3a9ea1b426dc245aff28b976a2af48ee759fdd9be374ce2bd2669b644f31e76c5f46a2e29a831 @@ -17007,6 +17008,16 @@ __metadata: languageName: node linkType: hard +"one-double-zero-core@npm:^1.0.0": + version: 1.0.0 + resolution: "one-double-zero-core@npm:1.0.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.25 + istanbul-lib-coverage: ^3.2.2 + checksum: 71c8b1d007cd6400efd4f19aa41ad725abec80eb9eb84cf76d7815e3787fa6aa718f6990a4689a289de886ff1c44da1d1983f70d8bd76a76ca8fd2b133b5c22b + languageName: node + linkType: hard + "onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2"