Skip to content

Commit

Permalink
Fix issue #498
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmorand committed Jun 23, 2024
1 parent ff146b4 commit 953a5c5
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 493 deletions.
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,24 @@ Here is a list of common options. Run `c8 --help` for the full list and document
| `-c`, `--config` | path to JSON configuration file | `string` | See above |
| `-r`, `--reporter` | coverage reporter(s) to use | `Array<string>` | `['text']` |
| `-o`, `--reports-dir`, `--report-dir` | directory where coverage reports will be output to | `string` | `./coverage` |
| `--all` | see [section below](#checking-for-full-source-coverage-using---all) for more info | `boolean` | `false` |
| `--src` | see [section below](#checking-for-full-source-coverage-using---all) for more info | `Array<string>` | `[process.cwd()]`|
| `-n`, `--include` | see [section below](#checking-for-full-source-coverage-using---all) for more info | `Array<string>` | `[]` (include all files) |
| `-x`, `--exclude` | see [section below](#checking-for-full-source-coverage-using---all) for more info | `Array<string>` | [list](https://github.com/istanbuljs/schema/blob/master/default-exclude.js) |
| `-n`, `--include` | see [section below](#checking-for-full-source-coverage) for more info | `Array<string>` | `[]` (include all files) |
| `-x`, `--exclude` | see [section below](#checking-for-full-source-coverage) for more info | `Array<string>` | [list](https://github.com/istanbuljs/schema/blob/master/default-exclude.js) |
| `--exclude-after-remap` | see [section below](#exclude-after-remap) for more info | `boolean` | `false` |
| `-e`, `--extension` | only files matching these extensions will show coverage | `string \| Array<string>` | [list](https://github.com/istanbuljs/schema/blob/master/default-extension.js) |
| `--skip-full` | do not show files with 100% statement, branch, and function coverage | `boolean` | `false` |
| `--check-coverage` | check whether coverage is within thresholds provided | `boolean` | `false` |
| `--per-file` | check thresholds per file | `boolean` | `false` |
| `--temp-directory` | directory V8 coverage data is written to and read from | `string` | `process.env.NODE_V8_COVERAGE` |
| `--clean` | should temp files be deleted before script execution | `boolean` | `true` |
| `--experimental-monocart` | see [section below](#using-monocart-coverage-reports-experimental) for more info | `boolean` | `false` |

## Checking for "full" source coverage using `--all`
## Checking for "full" source coverage

By default v8 will only give us coverage for files that were loaded by the engine. If there are source files in your
By default, v8 will only give us coverage for files that were loaded by the engine. If there are source files in your
project that are flexed in production but not in your tests, your coverage numbers will not reflect this. For example,
if your project's `main.js` loads `a.js` and `b.js` but your unit tests only load `a.js` your total coverage
could show as `100%` for `a.js` when in fact both `main.js` and `b.js` are uncovered.

By supplying `--all` to c8, all files in directories specified with `--src` (defaults to `cwd`) that pass the `--include`
and `--exclude` flag checks, will be loaded into the report. If any of those files remain uncovered they will be factored
into the report with a default of 0% coverage.
C8 overcomes this issue: all files that pass the `--include` and `--exclude` flag checks will be loaded into the report. If any of those files remain uncovered, they will be factored into the report with a default of 0% coverage.

## SourceMap Support

Expand Down
5 changes: 2 additions & 3 deletions lib/commands/check-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@ exports.handler = function (argv) {
}

const report = Report({
basePath: argv.basePath,
include: argv.include,
exclude: argv.exclude,
extension: argv.extension,
reporter: Array.isArray(argv.reporter) ? argv.reporter : [argv.reporter],
reportsDirectory: argv['reports-dir'],
tempDirectory: argv.tempDirectory,
watermarks: argv.watermarks,
resolve: argv.resolve,
omitRelative: argv.omitRelative,
wrapperLength: argv.wrapperLength,
all: argv.all
wrapperLength: argv.wrapperLength
})
exports.checkCoverages(argv, report)
}
Expand Down
6 changes: 1 addition & 5 deletions lib/commands/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ exports.outputReport = async function (argv) {
argv.statements = 100
}
const report = Report({
basePath: argv.basePath,
include: argv.include,
exclude: argv.exclude,
extension: argv.extension,
excludeAfterRemap: argv.excludeAfterRemap,
reporter: Array.isArray(argv.reporter) ? argv.reporter : [argv.reporter],
reportsDirectory: argv['reports-dir'],
Expand All @@ -31,11 +31,7 @@ exports.outputReport = async function (argv) {
resolve: argv.resolve,
omitRelative: argv.omitRelative,
wrapperLength: argv.wrapperLength,
all: argv.all,
allowExternal: argv.allowExternal,
src: argv.src,
skipFull: argv.skipFull,
excludeNodeModules: argv.excludeNodeModules,
mergeAsync: argv.mergeAsync,
monocartArgv: (argv.experimentalMonocart || process.env.EXPERIMENTAL_MONOCART) ? argv : null
})
Expand Down
53 changes: 19 additions & 34 deletions lib/parse-args.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const defaultExclude = require('@istanbuljs/schema/default-exclude')
const defaultExtension = require('@istanbuljs/schema/default-extension')
const findUp = require('find-up')
const { readFileSync } = require('fs')
const Yargs = require('yargs/yargs')
const { applyExtends } = require('yargs/helpers')
const parser = require('yargs-parser')
const { resolve } = require('path')
const { resolve, dirname } = require('path')

function buildYargs (withCommands = false) {
const configurationFile = findUp.sync(['.c8rc', '.c8rc.json', '.nycrc', '.nycrc.json'])

const yargs = Yargs([])
.usage('$0 [opts] [script] [opts]')
.options('config', {
Expand All @@ -18,7 +19,7 @@ function buildYargs (withCommands = false) {
const config = JSON.parse(readFileSync(path))
return applyExtends(config, process.cwd(), true)
},
default: () => findUp.sync(['.c8rc', '.c8rc.json', '.nycrc', '.nycrc.json'])
default: () => configurationFile
})
.option('reporter', {
alias: 'r',
Expand All @@ -32,25 +33,6 @@ function buildYargs (withCommands = false) {
describe: 'directory where coverage reports will be output to',
default: './coverage'
})
.options('all', {
default: false,
type: 'boolean',
group: 'Reporting options',
describe: 'supplying --all will cause c8 to consider all src files in the current working directory ' +
'when the determining coverage. Respects include/exclude.'
})
.options('src', {
default: undefined,
type: 'string',
group: 'Reporting options',
describe: 'supplying --src will override cwd as the default location where --all looks for src files. --src can be ' +
'supplied multiple times and each directory will be included. This allows for workspaces spanning multiple projects'
})
.option('exclude-node-modules', {
default: true,
type: 'boolean',
describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default'
})
.option('include', {
alias: 'n',
default: [],
Expand All @@ -63,12 +45,6 @@ function buildYargs (withCommands = false) {
group: 'Reporting options',
describe: 'a list of specific files and directories that should be excluded from coverage (glob patterns are supported)'
})
.option('extension', {
alias: 'e',
default: defaultExtension,
group: 'Reporting options',
describe: 'a list of specific file extensions that should be covered'
})
.option('exclude-after-remap', {
alias: 'a',
type: 'boolean',
Expand Down Expand Up @@ -146,12 +122,6 @@ function buildYargs (withCommands = false) {
type: 'boolean',
describe: 'omit any paths that are not absolute, e.g., internal/net.js'
})
.options('allowExternal', {
default: false,
type: 'boolean',
describe: 'supplying --allowExternal will cause c8 to allow files from outside of your cwd. This applies both to ' +
'files discovered in coverage temp files and also src files discovered if using the --all flag.'
})
.options('merge-async', {
default: false,
type: 'boolean',
Expand All @@ -166,9 +136,24 @@ function buildYargs (withCommands = false) {
.pkgConf('c8')
.demandCommand(1)
.check((argv) => {
if (typeof argv.exclude === 'string') {
argv.exclude = [argv.exclude]
}

if (typeof argv.include === 'string') {
argv.include = [argv.include]
}

if (!argv.tempDirectory) {
argv.tempDirectory = resolve(argv.reportsDir, 'tmp')
}

if (configurationFile) {
argv.basePath = dirname(configurationFile)
} else {
argv.basePath = process.cwd()
}

return true
})
.epilog('visit https://git.io/vHysA for list of available reporters')
Expand Down
122 changes: 43 additions & 79 deletions lib/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ try {
;({ readFile } = require('fs').promises)
}
const { readdirSync, readFileSync, statSync } = require('fs')
const { isAbsolute, resolve, extname } = require('path')
const { isAbsolute, resolve, join } = require('path')
const { pathToFileURL, fileURLToPath } = require('url')
const getSourceMapFromFile = require('./source-map-from-file')
// TODO: switch back to @c88/v8-coverage once patch is landed.
const v8toIstanbul = require('v8-to-istanbul')
const util = require('util')
const debuglog = util.debuglog('c8')
const { glob } = require('glob')

class Report {
constructor ({
basePath,
exclude,
extension,
excludeAfterRemap,
include,
reporter,
Expand All @@ -31,49 +32,38 @@ class Report {
omitRelative,
wrapperLength,
resolve: resolvePaths,
all,
src,
allowExternal = false,
skipFull,
excludeNodeModules,
mergeAsync,
monocartArgv
}) {
const resolvePattern = (pattern) => {
return join(basePath, pattern)
}

this.include = include ? include.map(resolvePattern) : [process.cwd()]
this.reporter = reporter
this.reporterOptions = reporterOptions || {}
this.reportsDirectory = reportsDirectory
this.tempDirectory = tempDirectory
this.watermarks = watermarks
this.resolve = resolvePaths
this.exclude = new Exclude({
exclude: exclude,
include: include,
extension: extension,
relativePath: !allowExternal,
excludeNodeModules: excludeNodeModules
exclude: exclude ? exclude.map(resolvePattern) : [],
include: this.include,
extension: false,
relativePath: false,
excludeNodeModules: false
})
this.excludeAfterRemap = excludeAfterRemap
this.shouldInstrumentCache = new Map()
this.omitRelative = omitRelative
this.sourceMapCache = {}
this.wrapperLength = wrapperLength
this.all = all
this.src = this._getSrc(src)
this.skipFull = skipFull
this.mergeAsync = mergeAsync
this.monocartArgv = monocartArgv
}

_getSrc (src) {
if (typeof src === 'string') {
return [src]
} else if (Array.isArray(src)) {
return src
} else {
return [process.cwd()]
}
}

async run () {
if (this.monocartArgv) {
return this.runMonocart()
Expand Down Expand Up @@ -151,22 +141,6 @@ class Report {
})
}

// --all: add empty coverage for all files
function getAllOptions () {
if (!argv.all) {
return
}

const src = argv.src
const workingDirs = Array.isArray(src) ? src : (typeof src === 'string' ? [src] : [process.cwd()])
return {
dir: workingDirs,
filter: (filePath) => {
return exclude.shouldInstrument(filePath)
}
}
}

function initPct (summary) {
Object.keys(summary).forEach(k => {
if (summary[k].pct === '') {
Expand All @@ -192,8 +166,6 @@ class Report {
inline: argv.inline,
lcov: argv.lcov,

all: getAllOptions(),

clean: argv.clean,

// use default value for istanbul
Expand Down Expand Up @@ -326,12 +298,10 @@ class Report {
}
}

if (this.all) {
const emptyReports = this._includeUncoveredFiles(fileIndex)
v8ProcessCovs.unshift({
result: emptyReports
})
}
const emptyReports = this._includeUncoveredFiles(fileIndex)
v8ProcessCovs.unshift({
result: emptyReports
})

return mergeProcessCovs(v8ProcessCovs)
}
Expand Down Expand Up @@ -374,54 +344,48 @@ class Report {
}
}

if (this.all) {
const emptyReports = this._includeUncoveredFiles(fileIndex)
const emptyReport = {
result: emptyReports
}

mergedCov = mergeProcessCovs([emptyReport, mergedCov])
const emptyReports = this._includeUncoveredFiles(fileIndex)
const emptyReport = {
result: emptyReports
}

mergedCov = mergeProcessCovs([emptyReport, mergedCov])

return mergedCov
}

/**
* Adds empty coverage reports to account for uncovered/untested code.
* This is only done when the `--all` flag is present.
*
* @param {Set} fileIndex list of files that have coverage
* @returns {Array} list of empty coverage reports
*/
_includeUncoveredFiles (fileIndex) {
const emptyReports = []
const workingDirs = this.src
const { extension } = this.exclude
const workingDirs = this.include
for (const workingDir of workingDirs) {
this.exclude.globSync(workingDir).forEach((f) => {
const fullPath = resolve(workingDir, f)
glob.sync(workingDir, {
ignore: this.exclude.exclude
}).forEach((fullPath) => {
if (!fileIndex.has(fullPath)) {
const ext = extname(fullPath)
if (extension.includes(ext)) {
const stat = statSync(fullPath)
const sourceMap = getSourceMapFromFile(fullPath)
if (sourceMap) {
this.sourceMapCache[pathToFileURL(fullPath)] = { data: sourceMap }
}
emptyReports.push({
scriptId: 0,
url: resolve(fullPath),
functions: [{
functionName: '(empty-report)',
ranges: [{
startOffset: 0,
endOffset: stat.size,
count: 0
}],
isBlockCoverage: true
}]
})
const stat = statSync(fullPath)
const sourceMap = getSourceMapFromFile(fullPath)
if (sourceMap) {
this.sourceMapCache[pathToFileURL(fullPath)] = { data: sourceMap }
}
emptyReports.push({
scriptId: 0,
url: resolve(fullPath),
functions: [{
functionName: '(empty-report)',
ranges: [{
startOffset: 0,
endOffset: stat.size,
count: 0
}],
isBlockCoverage: true
}]
})
}
})
}
Expand Down
Loading

0 comments on commit 953a5c5

Please sign in to comment.