forked from cypress-io/code-coverage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtask.js
233 lines (201 loc) · 7.06 KB
/
task.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// @ts-check
const istanbul = require('istanbul-lib-coverage')
const { join, resolve } = require('path')
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs')
const execa = require('execa')
const {
showNycInfo,
resolveRelativePaths,
checkAllPathsNotFound,
tryFindingLocalFiles,
readNycOptions,
includeAllFiles
} = require('./task-utils')
const { fixSourcePaths } = require('./support-utils')
const NYC = require('nyc')
const debug = require('debug')('code-coverage')
// these are standard folder and file names used by NYC tools
const processWorkingDirectory = process.cwd()
const outputFolder = '.nyc_output'
const coverageFolder = join(processWorkingDirectory, outputFolder)
const nycFilename = join(coverageFolder, 'out.json')
// there might be custom "nyc" options in the user package.json
// see https://github.com/istanbuljs/nyc#configuring-nyc
// potentially there might be "nyc" options in other configuration files
// it allows, but for now ignore those options
const pkgFilename = join(processWorkingDirectory, 'package.json')
const pkg = existsSync(pkgFilename)
? JSON.parse(readFileSync(pkgFilename, 'utf8'))
: {}
const scripts = pkg.scripts || {}
const DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME = 'coverage:report'
const customNycReportScript = scripts[DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME]
function saveCoverage(coverage) {
if (!existsSync(coverageFolder)) {
mkdirSync(coverageFolder)
debug('created folder %s for output coverage', coverageFolder)
}
writeFileSync(nycFilename, JSON.stringify(coverage, null, 2))
}
function maybePrintFinalCoverageFiles(folder) {
const jsonReportFilename = join(folder, 'coverage-final.json')
if (!existsSync(jsonReportFilename)) {
debug('Did not find final coverage file %s', jsonReportFilename)
return
}
debug('Final coverage in %s', jsonReportFilename)
const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8'))
const finalCoverageKeys = Object.keys(finalCoverage)
debug(
'There are %d key(s) in %s',
finalCoverageKeys.length,
jsonReportFilename
)
finalCoverageKeys.forEach(key => {
const s = finalCoverage[key].s || {}
const statements = Object.keys(s)
const totalStatements = statements.length
let coveredStatements = 0
statements.forEach(statementKey => {
if (s[statementKey]) {
coveredStatements += 1
}
})
const hasStatements = totalStatements > 0
const allCovered = coveredStatements === totalStatements
const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓'
debug(
'%s %s statements covered %d/%d',
coverageStatus,
key,
coveredStatements,
totalStatements
)
})
}
const tasks = {
/**
* Clears accumulated code coverage information.
*
* Interactive mode with "cypress open"
* - running a single spec or "Run all specs" needs to reset coverage
* Headless mode with "cypress run"
* - runs EACH spec separately, so we cannot reset the coverage
* or we will lose the coverage from previous specs.
*/
resetCoverage({ isInteractive }) {
if (isInteractive) {
debug('reset code coverage in interactive mode')
const coverageMap = istanbul.createCoverageMap({})
saveCoverage(coverageMap)
}
/*
Else:
in headless mode, assume the coverage file was deleted
before the `cypress run` command was called
example: rm -rf .nyc_output || true
*/
return null
},
/**
* Combines coverage information from single test
* with previously collected coverage.
*
* @param {string} sentCoverage Stringified coverage object sent by the test runner
* @returns {null} Nothing is returned from this task
*/
combineCoverage(sentCoverage) {
const coverage = JSON.parse(sentCoverage)
debug('parsed sent coverage')
fixSourcePaths(coverage)
const previous = existsSync(nycFilename)
? JSON.parse(readFileSync(nycFilename, 'utf8'))
: istanbul.createCoverageMap({})
const coverageMap = istanbul.createCoverageMap(previous)
coverageMap.merge(coverage)
saveCoverage(coverageMap)
debug('wrote coverage file %s', nycFilename)
return null
},
/**
* Saves coverage information as a JSON file and calls
* NPM script to generate HTML report
*/
coverageReport() {
if (!existsSync(nycFilename)) {
console.warn('Cannot find coverage file %s', nycFilename)
console.warn('Skipping coverage report')
return null
}
showNycInfo(nycFilename)
const allSourceFilesMissing = checkAllPathsNotFound(nycFilename)
if (allSourceFilesMissing) {
tryFindingLocalFiles(nycFilename)
}
resolveRelativePaths(nycFilename)
if (customNycReportScript) {
debug(
'saving coverage report using script "%s" from package.json, command: %s',
DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME,
customNycReportScript
)
debug('current working directory is %s', process.cwd())
return execa('npm', ['run', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME], {
stdio: 'inherit'
})
}
// https://github.com/istanbuljs/nyc#common-configuration-options
const nycReportOptions = readNycOptions(processWorkingDirectory)
if (nycReportOptions.exclude && !Array.isArray(nycReportOptions.exclude)) {
console.error('NYC options: %o', nycReportOptions)
throw new Error('Expected "exclude" to by an array')
}
// override a couple of options
nycReportOptions.tempDir = coverageFolder
if (nycReportOptions['report-dir']) {
nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir'])
}
// 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(nycFilename, nycReportOptions)
}
debug('calling NYC reporter with options %o', nycReportOptions)
debug('current working directory is %s', process.cwd())
const nyc = new NYC(nycReportOptions)
const returnReportFolder = () => {
const reportFolder = nycReportOptions['report-dir']
debug(
'after reporting, returning the report folder name %s',
reportFolder
)
maybePrintFinalCoverageFiles(reportFolder)
return reportFolder
}
return nyc.report().then(returnReportFolder)
}
}
/**
* Registers code coverage collection and reporting tasks.
* Sets an environment variable to tell the browser code that it can
* send the coverage.
* @example
```
// your plugins file
module.exports = (on, config) => {
require('cypress/code-coverage/task')(on, config)
// IMPORTANT to return the config object
// with the any changed environment variables
return config
}
```
*/
function registerCodeCoverageTasks(on, config) {
on('task', tasks)
// set a variable to let the hooks running in the browser
// know that they can send coverage commands
config.env.codeCoverageTasksRegistered = true
return config
}
module.exports = registerCodeCoverageTasks