From 65cb0c86a269163c48273c5f5dfbd1742413e252 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 11:14:16 -0400 Subject: [PATCH 1/7] add example where a file should not be covered --- examples/all-files/.babelrc | 3 ++ examples/all-files/README.md | 1 + examples/all-files/cypress.json | 4 ++ .../all-files/cypress/integration/spec.js | 12 +++++ examples/all-files/cypress/plugins/index.js | 5 +++ .../all-files/cypress/support/commands.js | 2 + examples/all-files/cypress/support/index.js | 1 + examples/all-files/index.html | 17 +++++++ examples/all-files/main.js | 3 ++ examples/all-files/not-covered.js | 7 +++ examples/all-files/package.json | 16 +++++++ examples/all-files/second.js | 7 +++ package-lock.json | 44 +++++-------------- package.json | 5 ++- 14 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 examples/all-files/.babelrc create mode 100644 examples/all-files/README.md create mode 100644 examples/all-files/cypress.json create mode 100644 examples/all-files/cypress/integration/spec.js create mode 100644 examples/all-files/cypress/plugins/index.js create mode 100644 examples/all-files/cypress/support/commands.js create mode 100644 examples/all-files/cypress/support/index.js create mode 100644 examples/all-files/index.html create mode 100644 examples/all-files/main.js create mode 100644 examples/all-files/not-covered.js create mode 100644 examples/all-files/package.json create mode 100644 examples/all-files/second.js diff --git a/examples/all-files/.babelrc b/examples/all-files/.babelrc new file mode 100644 index 00000000..7a016cf8 --- /dev/null +++ b/examples/all-files/.babelrc @@ -0,0 +1,3 @@ +{ + "plugins": ["istanbul"] +} diff --git a/examples/all-files/README.md b/examples/all-files/README.md new file mode 100644 index 00000000..e5faf288 --- /dev/null +++ b/examples/all-files/README.md @@ -0,0 +1 @@ +# example: all files diff --git a/examples/all-files/cypress.json b/examples/all-files/cypress.json new file mode 100644 index 00000000..5abc6bc7 --- /dev/null +++ b/examples/all-files/cypress.json @@ -0,0 +1,4 @@ +{ + "fixturesFolder": false, + "baseUrl": "http://localhost:1234" +} diff --git a/examples/all-files/cypress/integration/spec.js b/examples/all-files/cypress/integration/spec.js new file mode 100644 index 00000000..d7ca62b2 --- /dev/null +++ b/examples/all-files/cypress/integration/spec.js @@ -0,0 +1,12 @@ +/// +it('works', () => { + cy.visit('/') + cy.contains('Page body') + + cy.window() + .invoke('reverse', 'super') + .should('equal', 'repus') + + // application's code should be instrumented + cy.window().should('have.property', '__coverage__') +}) diff --git a/examples/all-files/cypress/plugins/index.js b/examples/all-files/cypress/plugins/index.js new file mode 100644 index 00000000..b17c48db --- /dev/null +++ b/examples/all-files/cypress/plugins/index.js @@ -0,0 +1,5 @@ +module.exports = (on, config) => { + require('../../../../task')(on, config) + on('file:preprocessor', require('../../../../use-babelrc')) + return config +} diff --git a/examples/all-files/cypress/support/commands.js b/examples/all-files/cypress/support/commands.js new file mode 100644 index 00000000..219920ee --- /dev/null +++ b/examples/all-files/cypress/support/commands.js @@ -0,0 +1,2 @@ +import '../../../../support' +console.log('this is commands file') diff --git a/examples/all-files/cypress/support/index.js b/examples/all-files/cypress/support/index.js new file mode 100644 index 00000000..b5c578c9 --- /dev/null +++ b/examples/all-files/cypress/support/index.js @@ -0,0 +1 @@ +require('./commands') diff --git a/examples/all-files/index.html b/examples/all-files/index.html new file mode 100644 index 00000000..993f0c18 --- /dev/null +++ b/examples/all-files/index.html @@ -0,0 +1,17 @@ + + Page body + + + + diff --git a/examples/all-files/main.js b/examples/all-files/main.js new file mode 100644 index 00000000..5dd69be2 --- /dev/null +++ b/examples/all-files/main.js @@ -0,0 +1,3 @@ +window.add = (a, b) => a + b + +window.sub = (a, b) => a - b diff --git a/examples/all-files/not-covered.js b/examples/all-files/not-covered.js new file mode 100644 index 00000000..39897541 --- /dev/null +++ b/examples/all-files/not-covered.js @@ -0,0 +1,7 @@ +// this file is NOT included from "index.html" +// thus it is not instrumented and not included +// in the final code coverage numbers +function throwsError() { + throw new Error('NO') +} +throwsError() diff --git a/examples/all-files/package.json b/examples/all-files/package.json new file mode 100644 index 00000000..9ad0717b --- /dev/null +++ b/examples/all-files/package.json @@ -0,0 +1,16 @@ +{ + "name": "example-all-files", + "description": "Report all files", + "scripts": { + "start": "../../node_modules/.bin/parcel serve index.html", + "cy:open": "../../node_modules/.bin/cypress open", + "dev": "../../node_modules/.bin/start-test 1234 cy:open" + }, + "nyc": { + "all": true, + "include": ["*.js"] + }, + "devDependencies": { + "@babel/core": "7.9.0" + } +} diff --git a/examples/all-files/second.js b/examples/all-files/second.js new file mode 100644 index 00000000..494a0c5f --- /dev/null +++ b/examples/all-files/second.js @@ -0,0 +1,7 @@ +// this file should be excluded from the final coverage numbers +// using "nyc.exclude" list in package.json +window.reverse = s => + s + .split('') + .reverse() + .join('') diff --git a/package-lock.json b/package-lock.json index 75cd32dc..86d316b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2976,7 +2976,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.3", "run-parallel": "^1.1.9" @@ -2985,8 +2984,7 @@ "@nodelib/fs.stat": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" } } }, @@ -3000,7 +2998,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.3", "fastq": "^1.6.0" @@ -4164,8 +4161,7 @@ "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, "array-unique": { "version": "0.3.2", @@ -6742,7 +6738,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "requires": { "path-type": "^4.0.0" }, @@ -6750,8 +6745,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" } } }, @@ -7558,7 +7552,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz", "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -8497,7 +8490,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", - "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -8510,14 +8502,12 @@ "@nodelib/fs.stat": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -8526,7 +8516,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -8540,7 +8529,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -8549,7 +8537,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -8557,20 +8544,17 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "merge2": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "dev": true + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==" }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.0.5" @@ -8580,7 +8564,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -8983,8 +8966,7 @@ "ignore": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==" }, "import-fresh": { "version": "2.0.0", @@ -15570,8 +15552,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "2.3.0", @@ -16967,8 +16948,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rgb-regex": { "version": "1.0.1", @@ -17003,8 +16983,7 @@ "run-parallel": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" }, "run-queue": { "version": "1.0.3", @@ -17582,8 +17561,7 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { "version": "0.0.4", diff --git a/package.json b/package.json index 7ffea21b..58ca69c3 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,9 @@ "@cypress/browserify-preprocessor": "2.2.1", "debug": "4.1.1", "execa": "4.0.0", - "nyc": "15.0.1", - "istanbul-lib-coverage": "3.0.0" + "globby": "11.0.0", + "istanbul-lib-coverage": "3.0.0", + "nyc": "15.0.1" }, "devDependencies": { "@babel/core": "7.9.0", From 4d07ead323aaff709c451ce15f43802a06457002 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 11:30:02 -0400 Subject: [PATCH 2/7] find files to include using globby --- examples/all-files/package.json | 4 +++- task-utils.js | 42 ++++++++++++++++++++++++++++++++- task.js | 8 ++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/examples/all-files/package.json b/examples/all-files/package.json index 9ad0717b..f5d09c4f 100644 --- a/examples/all-files/package.json +++ b/examples/all-files/package.json @@ -4,7 +4,9 @@ "scripts": { "start": "../../node_modules/.bin/parcel serve index.html", "cy:open": "../../node_modules/.bin/cypress open", - "dev": "../../node_modules/.bin/start-test 1234 cy:open" + "cy:run": "../../node_modules/.bin/cypress run", + "dev": "../../node_modules/.bin/start-test 1234 cy:open", + "e2e": "../../node_modules/.bin/start-test 1234 cy:run" }, "nyc": { "all": true, diff --git a/task-utils.js b/task-utils.js index 7abd6274..755c30ed 100644 --- a/task-utils.js +++ b/task-utils.js @@ -7,6 +7,7 @@ const { readFileSync, writeFileSync, existsSync } = require('fs') const { isAbsolute, resolve, join } = require('path') const debug = require('debug')('code-coverage') const chalk = require('chalk') +const globby = require('globby') function combineNycOptions({ pkgNycOptions, @@ -264,6 +265,44 @@ function tryFindingLocalFiles(nycFilename) { } } +/** + * If the website or unit tests did not load ALL files we need to + * include, then we should include the missing files ourselves + * before generating the report. + * + * @see https://github.com/cypress-io/code-coverage/issues/207 + */ +function includeAllFiles(nycOptions) { + debug('include all files options: %o', { + all: nycOptions.all, + include: nycOptions.include, + exclude: nycOptions.exclude + }) + + let patterns = [] + if (Array.isArray(nycOptions.include)) { + patterns = patterns.concat(nycOptions.include) + } else if (typeof nycOptions.include === 'string') { + patterns.push(nycOptions.include) + } + + if (Array.isArray(nycOptions.exclude)) { + const negated = nycOptions.exclude.map(s => '!' + s) + patterns = patterns.concat(negated) + } else if (typeof nycOptions.exclude === 'string') { + patterns.push('!' + nycOptions.exclude) + } + // always exclude node_modules + // https://github.com/istanbuljs/nyc#including-files-within-node_modules + patterns.push('!**/node_modules/**') + + debug('searching files to include using patterns %o', patterns) + + const allFiles = globby.sync(patterns) + debug('found these files %o', allFiles) + // TODO check if any of the files to include are missing from NYC output JSON file +} + module.exports = { showNycInfo, resolveRelativePaths, @@ -271,5 +310,6 @@ module.exports = { tryFindingLocalFiles, readNycOptions, combineNycOptions, - defaultNycOptions + defaultNycOptions, + includeAllFiles } diff --git a/task.js b/task.js index 08c72a71..7fb960e7 100644 --- a/task.js +++ b/task.js @@ -8,7 +8,8 @@ const { resolveRelativePaths, checkAllPathsNotFound, tryFindingLocalFiles, - readNycOptions + readNycOptions, + includeAllFiles } = require('./task-utils') const { fixSourcePaths } = require('./support-utils') const NYC = require('nyc') @@ -165,6 +166,11 @@ const tasks = { // seems nyc API really is using camel cased version nycReportOptions.reportDir = nycReportOptions['report-dir'] + if (nycReportOptions.all) { + debug('nyc needs to report on all included files') + includeAllFiles(nycReportOptions) + } + debug('calling NYC reporter with options %o', nycReportOptions) debug('current working directory is %s', process.cwd()) const nyc = new NYC(nycReportOptions) From e87a9e327a8e91c0adb32fbd8ce8502722166051 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 12:13:06 -0400 Subject: [PATCH 3/7] insert empty coverage for missed files --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++ examples/all-files/package.json | 3 ++- task-utils.js | 39 ++++++++++++++++++++++++++++++--- task.js | 2 +- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36f96851..fae97b78 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -354,6 +354,43 @@ workflows: ../../node_modules/.bin/only-covered main.js working_directory: examples/support-files + - cypress/run: + attach-workspace: true + name: example-all-files + requires: + - cypress/install + # there are no jobs to follow this one + # so no need to save the workspace files (saves time) + no-workspace: true + start: npm start --prefix examples/all-files + wait-on: 'http://localhost:1234' + command: npx cypress run --project examples/all-files + # store screenshots and videos + store_artifacts: true + post-steps: + - run: cat examples/all-files/.nyc_output/out.json + - run: cat examples/all-files/coverage/coverage-final.json + # store the created coverage report folder + # you can click on it in the CircleCI UI + # to see live static HTML site + - store_artifacts: + path: examples/all-files/coverage + # make sure the examples captures 100% of code + - run: + command: npx nyc report --check-coverage true --lines 100 + working_directory: examples/all-files + - run: + name: Check code coverage 📈 + # we will check the final coverage report + # to make sure it only has files we are interested in + # because there are files covered at 0 in the report + # TODO confirm "not-covered.js" is at 0/0 but included in the report + command: | + ../../node_modules/.bin/check-coverage main.js + ../../node_modules/.bin/check-coverage second.js + # ../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js + working_directory: examples/all-files + - cypress/run: attach-workspace: true name: example-exclude-files @@ -467,3 +504,4 @@ workflows: - example-exclude-files - example-docker-paths - example-use-webpack + - example-all-files diff --git a/examples/all-files/package.json b/examples/all-files/package.json index f5d09c4f..2c2175f4 100644 --- a/examples/all-files/package.json +++ b/examples/all-files/package.json @@ -6,7 +6,8 @@ "cy:open": "../../node_modules/.bin/cypress open", "cy:run": "../../node_modules/.bin/cypress run", "dev": "../../node_modules/.bin/start-test 1234 cy:open", - "e2e": "../../node_modules/.bin/start-test 1234 cy:run" + "e2e": "../../node_modules/.bin/start-test 1234 cy:run", + "report": "../../node_modules/.bin/nyc report" }, "nyc": { "all": true, diff --git a/task-utils.js b/task-utils.js index 755c30ed..3ab6cf0b 100644 --- a/task-utils.js +++ b/task-utils.js @@ -272,7 +272,7 @@ function tryFindingLocalFiles(nycFilename) { * * @see https://github.com/cypress-io/code-coverage/issues/207 */ -function includeAllFiles(nycOptions) { +function includeAllFiles(nycFilename, nycOptions) { debug('include all files options: %o', { all: nycOptions.all, include: nycOptions.include, @@ -298,9 +298,42 @@ function includeAllFiles(nycOptions) { debug('searching files to include using patterns %o', patterns) - const allFiles = globby.sync(patterns) + const allFiles = globby.sync(patterns, { absolute: true }) debug('found these files %o', allFiles) - // TODO check if any of the files to include are missing from NYC output JSON file + + const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) + const coverageKeys = Object.keys(nycCoverage) + const coveredPaths = coverageKeys.map(key => nycCoverage[key].path) + debug('coverage has the following paths %o', coveredPaths) + + let changed + allFiles.forEach(fullPath => { + if (coveredPaths.includes(fullPath)) { + // all good, this file exists in coverage object + return + } + debug('adding empty coverage for file %s', fullPath) + changed = true + // insert placeholder object for now + nycCoverage[fullPath] = { + path: fullPath, + statementMap: {}, + fnMap: {}, + branchMap: {}, + s: {}, + f: {}, + b: {} + } + }) + + if (changed) { + debug('saving updated file %s', nycFilename) + writeFileSync( + nycFilename, + JSON.stringify(nycCoverage, null, 2) + '\n', + 'utf8' + ) + } } module.exports = { diff --git a/task.js b/task.js index 7fb960e7..45c01859 100644 --- a/task.js +++ b/task.js @@ -168,7 +168,7 @@ const tasks = { if (nycReportOptions.all) { debug('nyc needs to report on all included files') - includeAllFiles(nycReportOptions) + includeAllFiles(nycFilename, nycReportOptions) } debug('calling NYC reporter with options %o', nycReportOptions) From e233b3493c1de001baccdbeb938e410a75cfc9ef Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 12:17:24 -0400 Subject: [PATCH 4/7] refactor a little --- common-utils.js | 37 +++++++++++++++++++++++++++++ cypress/integration/combine-spec.js | 2 +- task-utils.js | 35 +-------------------------- 3 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 common-utils.js diff --git a/common-utils.js b/common-utils.js new file mode 100644 index 00000000..d2c786a8 --- /dev/null +++ b/common-utils.js @@ -0,0 +1,37 @@ +// @ts-check +function combineNycOptions({ + pkgNycOptions, + nycrc, + nycrcJson, + defaultNycOptions +}) { + // last option wins + const nycOptions = Object.assign( + {}, + defaultNycOptions, + nycrc, + nycrcJson, + pkgNycOptions + ) + + if (typeof nycOptions.reporter === 'string') { + nycOptions.reporter = [nycOptions.reporter] + } + if (typeof nycOptions.extension === 'string') { + nycOptions.extension = [nycOptions.extension] + } + + return nycOptions +} + +const defaultNycOptions = { + 'report-dir': './coverage', + reporter: ['lcov', 'clover', 'json'], + extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], + excludeAfterRemap: true +} + +module.exports = { + combineNycOptions, + defaultNycOptions +} diff --git a/cypress/integration/combine-spec.js b/cypress/integration/combine-spec.js index 9c05d1a6..1d6718b6 100644 --- a/cypress/integration/combine-spec.js +++ b/cypress/integration/combine-spec.js @@ -1,4 +1,4 @@ -const { combineNycOptions, defaultNycOptions } = require('../../task-utils') +const { combineNycOptions, defaultNycOptions } = require('../../common-utils') describe('Combine NYC options', () => { it('overrides defaults', () => { const pkgNycOptions = { diff --git a/task-utils.js b/task-utils.js index 3ab6cf0b..2f0890ad 100644 --- a/task-utils.js +++ b/task-utils.js @@ -8,38 +8,7 @@ const { isAbsolute, resolve, join } = require('path') const debug = require('debug')('code-coverage') const chalk = require('chalk') const globby = require('globby') - -function combineNycOptions({ - pkgNycOptions, - nycrc, - nycrcJson, - defaultNycOptions -}) { - // last option wins - const nycOptions = Object.assign( - {}, - defaultNycOptions, - nycrc, - nycrcJson, - pkgNycOptions - ) - - if (typeof nycOptions.reporter === 'string') { - nycOptions.reporter = [nycOptions.reporter] - } - if (typeof nycOptions.extension === 'string') { - nycOptions.extension = [nycOptions.extension] - } - - return nycOptions -} - -const defaultNycOptions = { - 'report-dir': './coverage', - reporter: ['lcov', 'clover', 'json'], - extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], - excludeAfterRemap: true -} +const { combineNycOptions, defaultNycOptions } = require('./common-utils') function readNycOptions(workingDirectory) { const pkgFilename = join(workingDirectory, 'package.json') @@ -342,7 +311,5 @@ module.exports = { checkAllPathsNotFound, tryFindingLocalFiles, readNycOptions, - combineNycOptions, - defaultNycOptions, includeAllFiles } From 1a3de08a18c63f4518b60a65ac3f9c15cd211411 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 12:52:08 -0400 Subject: [PATCH 5/7] change the icon for files without any statements --- task.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/task.js b/task.js index 45c01859..866cec0c 100644 --- a/task.js +++ b/task.js @@ -63,10 +63,13 @@ function maybePrintFinalCoverageFiles(folder) { } }) + const hasStatements = totalStatements > 0 const allCovered = coveredStatements === totalStatements + const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓' + debug( '%s %s statements covered %d/%d', - allCovered ? '✅' : '⚠️', + coverageStatus, key, coveredStatements, totalStatements From 0f76cfbf6923b1e12b3ea5992eb16738a70bac3a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 13:22:15 -0400 Subject: [PATCH 6/7] confirm file not-covered is present in the final report --- .circleci/config.yml | 4 ++-- README.md | 2 ++ examples/all-files/package.json | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fae97b78..a00aca9c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -384,11 +384,11 @@ workflows: # we will check the final coverage report # to make sure it only has files we are interested in # because there are files covered at 0 in the report - # TODO confirm "not-covered.js" is at 0/0 but included in the report command: | ../../node_modules/.bin/check-coverage main.js ../../node_modules/.bin/check-coverage second.js - # ../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js + ../../node_modules/.bin/check-coverage not-covered.js + ../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js second.js not-covered.js working_directory: examples/all-files - cypress/run: diff --git a/README.md b/README.md index 78e8a331..a4bf6016 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,8 @@ For example, if you want to only include files in the `app` folder, but exclude } ``` +**Note:** if you have `all: true` NYC option set, this plugin will check the produced `.nyc_output/out.json` before generating the final report. If the `out.json` file does not have information for some files that should be there according to `include` list, then an empty placeholder will be included, see [PR 208](https://github.com/cypress-io/code-coverage/pull/208). + ## Disable plugin You can skip the client-side code coverage hooks by setting the environment variable `coverage` to `false`. diff --git a/examples/all-files/package.json b/examples/all-files/package.json index 2c2175f4..24cad696 100644 --- a/examples/all-files/package.json +++ b/examples/all-files/package.json @@ -10,8 +10,7 @@ "report": "../../node_modules/.bin/nyc report" }, "nyc": { - "all": true, - "include": ["*.js"] + "all": true }, "devDependencies": { "@babel/core": "7.9.0" From 2f7009066d72e93425f49ee40b54a7c84e6a0d99 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 21 Apr 2020 13:51:45 -0400 Subject: [PATCH 7/7] search using default extension masks if include is not present --- examples/all-files/package.json | 3 +- task-utils.js | 50 +++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/examples/all-files/package.json b/examples/all-files/package.json index 24cad696..59c6d0a8 100644 --- a/examples/all-files/package.json +++ b/examples/all-files/package.json @@ -10,7 +10,8 @@ "report": "../../node_modules/.bin/nyc report" }, "nyc": { - "all": true + "all": true, + "include": "*.js" }, "devDependencies": { "@babel/core": "7.9.0" diff --git a/task-utils.js b/task-utils.js index 2f0890ad..05b729df 100644 --- a/task-utils.js +++ b/task-utils.js @@ -235,24 +235,35 @@ function tryFindingLocalFiles(nycFilename) { } /** - * If the website or unit tests did not load ALL files we need to - * include, then we should include the missing files ourselves - * before generating the report. - * - * @see https://github.com/cypress-io/code-coverage/issues/207 + * Tries to find source files to be included in the final coverage report + * using NYC options: extension list, include and exclude. */ -function includeAllFiles(nycFilename, nycOptions) { +function findSourceFiles(nycOptions) { debug('include all files options: %o', { all: nycOptions.all, include: nycOptions.include, - exclude: nycOptions.exclude + exclude: nycOptions.exclude, + extension: nycOptions.extension }) + if (!Array.isArray(nycOptions.extension)) { + console.error( + 'Expected NYC "extension" option to be a list of file extensions' + ) + console.error(nycOptions) + return [] + } + let patterns = [] if (Array.isArray(nycOptions.include)) { patterns = patterns.concat(nycOptions.include) } else if (typeof nycOptions.include === 'string') { patterns.push(nycOptions.include) + } else { + debug('using default list of extensions') + nycOptions.extension.forEach(extension => { + patterns.push('**/*' + extension) + }) } if (Array.isArray(nycOptions.exclude)) { @@ -268,7 +279,30 @@ function includeAllFiles(nycFilename, nycOptions) { debug('searching files to include using patterns %o', patterns) const allFiles = globby.sync(patterns, { absolute: true }) - debug('found these files %o', allFiles) + return allFiles +} +/** + * If the website or unit tests did not load ALL files we need to + * include, then we should include the missing files ourselves + * before generating the report. + * + * @see https://github.com/cypress-io/code-coverage/issues/207 + */ +function includeAllFiles(nycFilename, nycOptions) { + if (!nycOptions.all) { + debug('NYC "all" option is not set, skipping including all files') + return + } + + const allFiles = findSourceFiles(nycOptions) + if (debug.enabled) { + debug('found %d file(s)', allFiles.length) + console.error(allFiles.join('\n')) + } + if (!allFiles.length) { + debug('no files found, hoping for the best') + return + } const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage)