/
v8-to-istanbul.js
226 lines (196 loc) · 8.25 KB
/
v8-to-istanbul.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
/* global describe, it, beforeEach, afterEach */
const { readdirSync, lstatSync, writeFileSync, readFileSync } = require('fs')
const path = require('path')
const { pathToFileURL } = require('url')
const runFixture = require('./utils/run-fixture')
const V8ToIstanbul = require('../lib/v8-to-istanbul')
const crypto = require('crypto')
const os = require('os')
const sourcemap = require('source-map')
const assert = require('assert')
require('tap').mochaGlobals()
const should = require('should')
describe('V8ToIstanbul', async () => {
describe('constructor', () => {
it('creates line instance for each line in V8ToIstanbul', async () => {
const v8ToIstanbul = new V8ToIstanbul(
require.resolve('./fixtures/scripts/functions.js')
)
await v8ToIstanbul.load()
v8ToIstanbul.covSources[0].source.lines.length.should.equal(48)
v8ToIstanbul.covSources.length.should.equal(1)
v8ToIstanbul.wrapperLength.should.equal(0) // common-js header.
v8ToIstanbul.destroy()
})
it('handles ESM style paths', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/functions.js')).href,
0
)
await v8ToIstanbul.load()
v8ToIstanbul.covSources[0].source.lines.length.should.equal(48)
v8ToIstanbul.covSources.length.should.equal(1)
v8ToIstanbul.wrapperLength.should.equal(0) // ESM header.
v8ToIstanbul.destroy()
})
it('handles source maps with sourceRoot', async () => {
const sourceFileName = 'sourcemap-source.js'
const sourceRoot = path.dirname(require.resolve(`./fixtures/scripts/${sourceFileName}`))
const absoluteSourceFilePath = path.join(sourceRoot, sourceFileName)
const map = new sourcemap.SourceMapGenerator({
file: sourceFileName,
sourceRoot
})
map.addMapping({
source: sourceFileName,
original: { line: 1, column: 1 },
generated: { line: 1, column: 1 }
})
map.setSourceContent(sourceFileName, readFileSync(absoluteSourceFilePath).toString())
const base64Sourcemap = Buffer.from(map.toString()).toString('base64')
const source = `const foo = "bar";
${'//'}${'#'} sourceMappingURL=data:application/json;base64,${base64Sourcemap}
`
const tmpPath = path.join(os.tmpdir(), crypto.randomBytes(4).readUInt32LE(0) + '.js')
writeFileSync(tmpPath, source)
const v8ToIstanbul = new V8ToIstanbul(tmpPath)
await v8ToIstanbul.load()
v8ToIstanbul.path.should.equal(absoluteSourceFilePath)
v8ToIstanbul.destroy()
})
it('handles sourceContent', async () => {
const sourceFileName = 'sourcemap-source.js'
const sourceRoot = path.dirname(require.resolve(`./fixtures/scripts/${sourceFileName}`))
const absoluteSourceFilePath = path.join(sourceRoot, sourceFileName)
const map = new sourcemap.SourceMapGenerator({
file: sourceFileName
})
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 1, column: 1 },
source: sourceFileName
})
map.setSourceContent(sourceFileName, readFileSync(absoluteSourceFilePath).toString())
const source = 'const foo = "bar";'
const tmpPath = path.join(os.tmpdir(), crypto.randomBytes(4).readUInt32LE(0) + '.js')
writeFileSync(tmpPath, source)
const sources = {
sourceMap: {
sourcemap: map.toJSON()
}
}
const v8ToIstanbul = new V8ToIstanbul(tmpPath, undefined, sources)
await v8ToIstanbul.load()
// if the source is transpiled and since we didn't inline the source map into the transpiled source file
// that means it was bale to access the content via the provided sources object
v8ToIstanbul.sourceTranspiled.should.not.be.undefined()
v8ToIstanbul.destroy()
})
it('should clamp line source column >= 0', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/needs-compile.compiled.js')).href,
0
)
// read the file and find the first end of line char
const fileBody = readFileSync(require.resolve('./fixtures/scripts/needs-compile.compiled.js')).toString()
const matchedNewLineChar = fileBody.match(/(?<=\r?\n)/u).index
// this isn't an assertion for the test so much as it is an assertion that the
// test fixture hasn't be reverted from \r\n to \n.
assert(fileBody.substring(matchedNewLineChar - 2, matchedNewLineChar) === '\r\n', 'The test fixture is misconfigured!')
await v8ToIstanbul.load()
// apply a fake range that starts with the matched new line
// (these ranges can occur on v8 running on windows) and verify
// coverage is applied correctly. CovLine will have a
// gap between the endCol of the previous line ending on \r and startCol of the
// next line. This would cause -1 to be sent for the source map lookup.
// This would cause source map translation to throw
v8ToIstanbul.applyCoverage([{
functionName: 'fake',
ranges: [{
startOffset: matchedNewLineChar - 1,
endOffset: matchedNewLineChar + 10
}]
}])
v8ToIstanbul.destroy()
})
it('should exclude files when passing excludePath', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/sourcemap-multisource.js')).href,
0,
undefined,
path => path.indexOf('bootstrap') > -1
)
await v8ToIstanbul.load()
v8ToIstanbul.applyCoverage([{
functionName: 'fake',
ranges: [{
startOffset: 0,
endOffset: 1
}]
}])
Object.keys(v8ToIstanbul.toIstanbul()).should.eql(['/src/index.ts', '/src/utils.ts'].map(path.normalize))
v8ToIstanbul.destroy()
})
})
describe('source map format edge cases', () => {
let consoleWarn
beforeEach(() => {
consoleWarn = console.warn
console.warn = () => { throw new Error('Test should not invoke console.warn') }
})
afterEach(() => {
console.warn = consoleWarn
})
it('should handle empty sources in a sourcemap', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/empty.compiled.js')).href,
0
)
await v8ToIstanbul.load()
v8ToIstanbul.destroy()
})
it('should handle relative sourceRoots correctly', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/relative-source-root.compiled.js')).href,
0
)
await v8ToIstanbul.load()
assert(v8ToIstanbul.path.includes(path.normalize('v8-to-istanbul/test/fixtures/one-up/relative-source-root.js')))
v8ToIstanbul.destroy()
})
it('should handles source maps with multiple sources', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/sourcemap-multisource.js')).href,
0
)
await v8ToIstanbul.load()
v8ToIstanbul.covSources.length.should.equal(3)
Object.keys(v8ToIstanbul.toIstanbul()).should.eql(['/webpack/bootstrap', '/src/index.ts', '/src/utils.ts'].map(path.normalize))
v8ToIstanbul.destroy()
})
})
it('destroy cleans up source map', async () => {
const v8ToIstanbul = new V8ToIstanbul(
pathToFileURL(require.resolve('./fixtures/scripts/empty.compiled.js')).href
)
await v8ToIstanbul.load()
// assertion only to check test data and setup - source map must be loaded,
// otherwise destroy would have no effect anyway
assert(v8ToIstanbul.sourceMap !== undefined, 'Test fixture must load a source map')
v8ToIstanbul.destroy()
should.not.exist(v8ToIstanbul.sourceMap)
})
// execute JavaScript files in fixtures directory; these
// files contain the raw v8 output along with a set of
// assertions. the original scripts can be found in the
// fixtures/scripts folder.
const fixtureRoot = path.resolve(__dirname, './fixtures')
for (const file of readdirSync(fixtureRoot)) {
const fixturePath = path.resolve(fixtureRoot, file)
const stats = lstatSync(fixturePath)
if (stats.isFile()) {
const fixture = require(fixturePath)
await runFixture(fixture)
}
}
})