Skip to content

Commit

Permalink
feat: adds --all functionality (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
j03m authored and bcoe committed Nov 28, 2019
1 parent 97b9769 commit 2eb631e
Show file tree
Hide file tree
Showing 25 changed files with 313 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,3 +3,4 @@ node_modules
.nyc_output
coverage
tmp
.idea
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -16,6 +16,16 @@ c8 node foo.js

The above example will output coverage metrics for `foo.js`.

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

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 `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 report

run `c8 report` to regenerate reports after `c8` has already been run.
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/report.js
Expand Up @@ -19,7 +19,8 @@ exports.outputReport = async function (argv) {
watermarks: argv.watermarks,
resolve: argv.resolve,
omitRelative: argv.omitRelative,
wrapperLength: argv.wrapperLength
wrapperLength: argv.wrapperLength,
all: argv.all
})
await report.run()
if (argv.checkCoverage) checkCoverages(argv, report)
Expand Down
6 changes: 6 additions & 0 deletions lib/parse-args.js
Expand Up @@ -83,6 +83,12 @@ function buildYargs (withCommands = false) {
type: 'boolean',
describe: 'should temp files be deleted before script execution'
})
.options('all', {
default: false,
type: 'boolean',
describe: 'supplying --all will cause c8 to consider all src files in the current working directory ' +
'when the determining coverage. Respects include/exclude.'
})
.pkgConf('c8')
.config(config)
.demandCommand(1)
Expand Down
55 changes: 48 additions & 7 deletions lib/report.js
Expand Up @@ -3,8 +3,9 @@ const furi = require('furi')
const libCoverage = require('istanbul-lib-coverage')
const libReport = require('istanbul-lib-report')
const reports = require('istanbul-reports')
const { readdirSync, readFileSync } = require('fs')
const { isAbsolute, resolve } = require('path')
const { readdirSync, readFileSync, statSync } = require('fs')
const { isAbsolute, resolve, extname } = require('path')
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 isCjsEsmBridgeCov = require('./is-cjs-esm-bridge')
Expand All @@ -19,7 +20,8 @@ class Report {
watermarks,
omitRelative,
wrapperLength,
resolve: resolvePaths
resolve: resolvePaths,
all
}) {
this.reporter = reporter
this.reportsDirectory = reportsDirectory
Expand All @@ -33,6 +35,8 @@ class Report {
this.omitRelative = omitRelative
this.sourceMapCache = {}
this.wrapperLength = wrapperLength
this.all = all
this.src = process.cwd()
}

async run () {
Expand All @@ -56,8 +60,8 @@ class Report {
// use-case.
if (this._allCoverageFiles) return this._allCoverageFiles

const map = libCoverage.createCoverageMap()
const v8ProcessCov = this._getMergedProcessCov()
const map = libCoverage.createCoverageMap({})
const resultCountPerPath = new Map()
const possibleCjsEsmBridges = new Map()

Expand Down Expand Up @@ -94,7 +98,6 @@ class Report {
map.merge(converter.toIstanbul())
}
}

this._allCoverageFiles = map
return this._allCoverageFiles
}
Expand Down Expand Up @@ -139,14 +142,50 @@ class Report {
_getMergedProcessCov () {
const { mergeProcessCovs } = require('@bcoe/v8-coverage')
const v8ProcessCovs = []
const fileIndex = new Set() // Set<string>
for (const v8ProcessCov of this._loadReports()) {
if (this._isCoverageObject(v8ProcessCov)) {
if (v8ProcessCov['source-map-cache']) {
Object.assign(this.sourceMapCache, v8ProcessCov['source-map-cache'])
}
v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov))
v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov, fileIndex))
}
}

if (this.all) {
const emptyReports = []
v8ProcessCovs.unshift({
result: emptyReports
})
const workingDir = process.cwd()
this.exclude.globSync(workingDir).forEach((f) => {
const fullPath = resolve(workingDir, f)
if (!fileIndex.has(fullPath)) {
const ext = extname(f)
if (ext === '.js' || ext === '.ts' || ext === '.mjs') {
const stat = statSync(f)
const sourceMap = getSourceMapFromFile(f)
if (sourceMap !== undefined) {
this.sourceMapCache[`file://${fullPath}`] = { data: JSON.parse(readFileSync(sourceMap).toString()) }
}
emptyReports.push({
scriptId: 0,
url: resolve(f),
functions: [{
functionName: '(empty-report)',
ranges: [{
startOffset: 0,
endOffset: stat.size,
count: 0
}],
isBlockCoverage: true
}]
})
}
}
})
}

return mergeProcessCovs(v8ProcessCovs)
}

Expand Down Expand Up @@ -193,15 +232,17 @@ class Report {
* There is no deep cloning.
*
* @param v8ProcessCov V8 process coverage to normalize.
* @param fileIndex a Set<string> of paths discovered in coverage
* @return {v8ProcessCov} Normalized V8 process coverage.
* @private
*/
_normalizeProcessCov (v8ProcessCov) {
_normalizeProcessCov (v8ProcessCov, fileIndex) {
const result = []
for (const v8ScriptCov of v8ProcessCov.result) {
if (/^file:\/\//.test(v8ScriptCov.url)) {
try {
v8ScriptCov.url = furi.toSysPath(v8ScriptCov.url)
fileIndex.add(v8ScriptCov.url)
} catch (err) {
console.warn(err)
continue
Expand Down
25 changes: 25 additions & 0 deletions lib/source-map-from-file.js
@@ -0,0 +1,25 @@
const { isAbsolute, join, dirname } = require('path')
const { readFileSync } = require('fs')
/**
* Extract the sourcemap url from a source file
* reference: https://sourcemaps.info/spec.html
* @param {String} file - compilation target file
* @returns {String} full path to source map file
* @private
*/
function getSourceMapFromFile (file) {
const fileBody = readFileSync(file).toString()
const sourceMapLineRE = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg
const results = fileBody.match(sourceMapLineRE)
if (results !== null) {
const sourceMap = results[results.length - 1].split('=')[1]
if (isAbsolute(sourceMap)) {
return sourceMap
} else {
const base = dirname(file)
return join(base, sourceMap)
}
}
}

module.exports = getSourceMapFromFile
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -61,4 +61,4 @@
"bin",
"LICENSE"
]
}
}
8 changes: 8 additions & 0 deletions test/fixtures/all/ts-compiled/dir/unloaded.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/all/ts-compiled/dir/unloaded.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/fixtures/all/ts-compiled/dir/unloaded.ts
@@ -0,0 +1,5 @@
export default function Unloaded(){
return 'Never loaded :('
}

console.log("This file shouldn't have been evaluated")
23 changes: 23 additions & 0 deletions test/fixtures/all/ts-compiled/loaded.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/all/ts-compiled/loaded.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions test/fixtures/all/ts-compiled/loaded.ts
@@ -0,0 +1,19 @@
export default function getString(i){
if (typeof i === 'number'){
if (isNaN(i)){
return 'NaN'
}
else if (i === 0){
return 'zero'
}
else if (i > 0){
return 'positive'
}
else {
return 'negative'
}
}
else {
return 'wat?'
}
}
7 changes: 7 additions & 0 deletions test/fixtures/all/ts-compiled/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/all/ts-compiled/main.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/all/ts-compiled/main.ts
@@ -0,0 +1,4 @@
import getString from "./loaded"
console.log(getString(0))
console.log(getString(1))
console.log(getString(-1))
5 changes: 5 additions & 0 deletions test/fixtures/all/ts-only/dir/unloaded.ts
@@ -0,0 +1,5 @@
export default function Unloaded(){
return 'Never loaded :('
}

console.log("This file shouldn't have been evaluated")
19 changes: 19 additions & 0 deletions test/fixtures/all/ts-only/loaded.ts
@@ -0,0 +1,19 @@
export default function getString(i){
if (typeof i === 'number'){
if (isNaN(i)){
return 'NaN'
}
else if (i === 0){
return 'zero'
}
else if (i > 0){
return 'positive'
}
else {
return 'negative'
}
}
else {
return 'wat?'
}
}
4 changes: 4 additions & 0 deletions test/fixtures/all/ts-only/main.ts
@@ -0,0 +1,4 @@
import getString from "./loaded"
console.log(getString(0))
console.log(getString(1))
console.log(getString(-1))
5 changes: 5 additions & 0 deletions test/fixtures/all/vanilla/dir/unloaded.js
@@ -0,0 +1,5 @@
module.exports = function Unloaded(){
return 'Never loaded :('
}

console.log("This file shouldn't have been evaluated")
19 changes: 19 additions & 0 deletions test/fixtures/all/vanilla/loaded.js
@@ -0,0 +1,19 @@
module.exports = function getString(i){
if (typeof i === 'number'){
if (isNaN(i)){
return 'NaN'
}
else if (i === 0){
return 'zero'
}
else if (i > 0){
return 'positive'
}
else {
return 'negative'
}
}
else {
return 'wat?'
}
}
4 changes: 4 additions & 0 deletions test/fixtures/all/vanilla/main.js
@@ -0,0 +1,4 @@
const loaded = require('./loaded.js');
console.log(loaded(0))
console.log(loaded(1))
console.log(loaded(-1))

0 comments on commit 2eb631e

Please sign in to comment.