Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds --all functionality #158

Merged
merged 7 commits into from Nov 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost the {} here. Not sure if that is an issue.

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'
bcoe marked this conversation as resolved.
Show resolved Hide resolved
}
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))