1'use strict'; 2 3if (!process.features.inspector) return; 4 5const common = require('../common'); 6const assert = require('assert'); 7const { dirname } = require('path'); 8const fs = require('fs'); 9const path = require('path'); 10const { spawnSync } = require('child_process'); 11const { pathToFileURL } = require('url'); 12 13const tmpdir = require('../common/tmpdir'); 14tmpdir.refresh(); 15 16let dirc = 0; 17function nextdir() { 18 return process.env.NODE_V8_COVERAGE || 19 path.join(tmpdir.path, `source_map_${++dirc}`); 20} 21 22// Outputs source maps when event loop is drained, with no async logic. 23{ 24 const coverageDirectory = nextdir(); 25 const output = spawnSync(process.execPath, [ 26 require.resolve('../fixtures/source-map/basic'), 27 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 28 if (output.status !== 0) { 29 console.log(output.stderr.toString()); 30 } 31 assert.strictEqual(output.status, 0); 32 assert.strictEqual(output.stderr.toString(), ''); 33 const sourceMap = getSourceMapFromCache('basic.js', coverageDirectory); 34 assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/418'); 35} 36 37// Outputs source maps when process.kill(process.pid, "SIGINT"); exits process. 38{ 39 const coverageDirectory = nextdir(); 40 const output = spawnSync(process.execPath, [ 41 require.resolve('../fixtures/source-map/sigint'), 42 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 43 if (!common.isWindows) { 44 if (output.signal !== 'SIGINT') { 45 console.log(output.stderr.toString()); 46 } 47 assert.strictEqual(output.signal, 'SIGINT'); 48 } 49 assert.strictEqual(output.stderr.toString(), ''); 50 const sourceMap = getSourceMapFromCache('sigint.js', coverageDirectory); 51 assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/402'); 52} 53 54// Outputs source maps when source-file calls process.exit(1). 55{ 56 const coverageDirectory = nextdir(); 57 const output = spawnSync(process.execPath, [ 58 require.resolve('../fixtures/source-map/exit-1'), 59 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 60 assert.strictEqual(output.stderr.toString(), ''); 61 const sourceMap = getSourceMapFromCache('exit-1.js', coverageDirectory); 62 assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/404'); 63} 64 65// Outputs source-maps for esm module. 66{ 67 const coverageDirectory = nextdir(); 68 const output = spawnSync(process.execPath, [ 69 '--no-warnings', 70 require.resolve('../fixtures/source-map/esm-basic.mjs'), 71 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 72 assert.strictEqual(output.stderr.toString(), ''); 73 const sourceMap = getSourceMapFromCache('esm-basic.mjs', coverageDirectory); 74 assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/405'); 75} 76 77// Loads source-maps with relative path from .map file on disk. 78{ 79 const coverageDirectory = nextdir(); 80 const output = spawnSync(process.execPath, [ 81 require.resolve('../fixtures/source-map/disk-relative-path'), 82 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 83 assert.strictEqual(output.status, 0); 84 assert.strictEqual(output.stderr.toString(), ''); 85 const sourceMap = getSourceMapFromCache( 86 'disk-relative-path.js', 87 coverageDirectory 88 ); 89 // Source-map should have been loaded from disk and sources should have been 90 // rewritten, such that they're absolute paths. 91 assert.strictEqual( 92 dirname(pathToFileURL( 93 require.resolve('../fixtures/source-map/disk-relative-path')).href), 94 dirname(sourceMap.data.sources[0]) 95 ); 96} 97 98// Loads source-maps from inline data URL. 99{ 100 const coverageDirectory = nextdir(); 101 const output = spawnSync(process.execPath, [ 102 require.resolve('../fixtures/source-map/inline-base64.js'), 103 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 104 assert.strictEqual(output.status, 0); 105 assert.strictEqual(output.stderr.toString(), ''); 106 const sourceMap = getSourceMapFromCache( 107 'inline-base64.js', 108 coverageDirectory 109 ); 110 // base64 JSON should have been decoded, and paths to sources should have 111 // been rewritten such that they're absolute: 112 assert.strictEqual( 113 dirname(pathToFileURL( 114 require.resolve('../fixtures/source-map/inline-base64')).href), 115 dirname(sourceMap.data.sources[0]) 116 ); 117} 118 119// base64 encoding error does not crash application. 120{ 121 const coverageDirectory = nextdir(); 122 const output = spawnSync(process.execPath, [ 123 require.resolve('../fixtures/source-map/inline-base64-type-error.js'), 124 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 125 assert.strictEqual(output.status, 0); 126 assert.strictEqual(output.stderr.toString(), ''); 127 const sourceMap = getSourceMapFromCache( 128 'inline-base64-type-error.js', 129 coverageDirectory 130 ); 131 132 assert.strictEqual(sourceMap.data, null); 133} 134 135// JSON error does not crash application. 136{ 137 const coverageDirectory = nextdir(); 138 const output = spawnSync(process.execPath, [ 139 require.resolve('../fixtures/source-map/inline-base64-json-error.js'), 140 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 141 assert.strictEqual(output.status, 0); 142 assert.strictEqual(output.stderr.toString(), ''); 143 const sourceMap = getSourceMapFromCache( 144 'inline-base64-json-error.js', 145 coverageDirectory 146 ); 147 148 assert.strictEqual(sourceMap.data, null); 149} 150 151// Does not apply source-map to stack trace if --experimental-modules 152// is not set. 153{ 154 const output = spawnSync(process.execPath, [ 155 require.resolve('../fixtures/source-map/uglify-throw.js'), 156 ]); 157 assert.strictEqual( 158 output.stderr.toString().match(/.*uglify-throw-original\.js:5:9/), 159 null 160 ); 161 assert.strictEqual( 162 output.stderr.toString().match(/.*uglify-throw-original\.js:9:3/), 163 null 164 ); 165} 166 167// Applies source-maps generated by uglifyjs to stack trace. 168{ 169 const output = spawnSync(process.execPath, [ 170 '--enable-source-maps', 171 require.resolve('../fixtures/source-map/uglify-throw.js'), 172 ]); 173 assert.match( 174 output.stderr.toString(), 175 /.*uglify-throw-original\.js:5:9/ 176 ); 177 assert.match( 178 output.stderr.toString(), 179 /.*uglify-throw-original\.js:9:3/ 180 ); 181 assert.match(output.stderr.toString(), /at Hello/); 182} 183 184// Applies source-maps generated by tsc to stack trace. 185{ 186 const output = spawnSync(process.execPath, [ 187 '--enable-source-maps', 188 require.resolve('../fixtures/source-map/typescript-throw.js'), 189 ]); 190 assert.ok(output.stderr.toString().match(/.*typescript-throw\.ts:18:11/)); 191 assert.ok(output.stderr.toString().match(/.*typescript-throw\.ts:24:1/)); 192} 193 194// Applies source-maps generated by babel to stack trace. 195{ 196 const output = spawnSync(process.execPath, [ 197 '--enable-source-maps', 198 require.resolve('../fixtures/source-map/babel-throw.js'), 199 ]); 200 assert.ok( 201 output.stderr.toString().match(/.*babel-throw-original\.js:18:31/) 202 ); 203} 204 205// Applies source-maps generated by nyc to stack trace. 206{ 207 const output = spawnSync(process.execPath, [ 208 '--enable-source-maps', 209 require.resolve('../fixtures/source-map/istanbul-throw.js'), 210 ]); 211 assert.ok( 212 output.stderr.toString().match(/.*istanbul-throw-original\.js:5:9/) 213 ); 214 assert.ok( 215 output.stderr.toString().match(/.*istanbul-throw-original\.js:9:3/) 216 ); 217} 218 219// Applies source-maps in esm modules to stack trace. 220{ 221 const output = spawnSync(process.execPath, [ 222 '--enable-source-maps', 223 require.resolve('../fixtures/source-map/babel-esm.mjs'), 224 ]); 225 assert.ok( 226 output.stderr.toString().match(/.*babel-esm-original\.mjs:9:29/) 227 ); 228} 229 230// Does not persist url parameter if source-map has been parsed. 231{ 232 const coverageDirectory = nextdir(); 233 spawnSync(process.execPath, [ 234 require.resolve('../fixtures/source-map/inline-base64.js'), 235 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 236 const sourceMap = getSourceMapFromCache( 237 'inline-base64.js', 238 coverageDirectory 239 ); 240 assert.strictEqual(sourceMap.url, null); 241} 242 243// Persists line lengths for in-memory representation of source file. 244{ 245 const coverageDirectory = nextdir(); 246 spawnSync(process.execPath, [ 247 require.resolve('../fixtures/source-map/istanbul-throw.js'), 248 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 249 const sourceMap = getSourceMapFromCache( 250 'istanbul-throw.js', 251 coverageDirectory 252 ); 253 if (common.checkoutEOL === '\r\n') { 254 assert.deepStrictEqual(sourceMap.lineLengths, [1086, 31, 185, 649, 0]); 255 } else { 256 assert.deepStrictEqual(sourceMap.lineLengths, [1085, 30, 184, 648, 0]); 257 } 258} 259 260// trace.length === 0 . 261{ 262 const output = spawnSync(process.execPath, [ 263 '--enable-source-maps', 264 require.resolve('../fixtures/source-map/emptyStackError.js'), 265 ]); 266 267 assert.ok( 268 output.stderr.toString().match('emptyStackError') 269 ); 270} 271 272// Does not attempt to apply path resolution logic to absolute URLs 273// with schemes. 274// Refs: https://github.com/webpack/webpack/issues/9601 275// Refs: https://sourcemaps.info/spec.html#h.75yo6yoyk7x5 276{ 277 const output = spawnSync(process.execPath, [ 278 '--enable-source-maps', 279 require.resolve('../fixtures/source-map/webpack.js'), 280 ]); 281 // Error in original context of source content: 282 assert.match( 283 output.stderr.toString(), 284 /throw new Error\('oh no!'\)\r?\n.*\^/ 285 ); 286 // Rewritten stack trace: 287 assert.match(output.stderr.toString(), /webpack:\/\/\/webpack\.js:14:9/); 288 assert.match(output.stderr.toString(), /at functionD.*14:9/); 289 assert.match(output.stderr.toString(), /at functionC.*10:3/); 290} 291 292// Stores and applies source map associated with file that throws while 293// being required. 294{ 295 const coverageDirectory = nextdir(); 296 const output = spawnSync(process.execPath, [ 297 '--enable-source-maps', 298 require.resolve('../fixtures/source-map/throw-on-require-entry.js'), 299 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 300 const sourceMap = getSourceMapFromCache( 301 'throw-on-require.js', 302 coverageDirectory 303 ); 304 // Rewritten stack trace. 305 assert.match(output.stderr.toString(), /throw-on-require\.ts:9:9/); 306 // Source map should have been serialized. 307 assert.ok(sourceMap); 308} 309 310// Does not throw TypeError when primitive value is thrown. 311{ 312 const coverageDirectory = nextdir(); 313 const output = spawnSync(process.execPath, [ 314 '--enable-source-maps', 315 require.resolve('../fixtures/source-map/throw-string.js'), 316 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 317 const sourceMap = getSourceMapFromCache( 318 'throw-string.js', 319 coverageDirectory 320 ); 321 // Original stack trace. 322 assert.match(output.stderr.toString(), /goodbye/); 323 // Source map should have been serialized. 324 assert.ok(sourceMap); 325} 326 327// Does not throw TypeError when exception occurs as result of missing named 328// export. 329{ 330 const coverageDirectory = nextdir(); 331 const output = spawnSync(process.execPath, [ 332 '--enable-source-maps', 333 require.resolve('../fixtures/source-map/esm-export-missing.mjs'), 334 ], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } }); 335 const sourceMap = getSourceMapFromCache( 336 'esm-export-missing.mjs', 337 coverageDirectory 338 ); 339 // Module loader error displayed. 340 assert.match(output.stderr.toString(), 341 /does not provide an export named 'Something'/); 342 // Source map should have been serialized. 343 assert.ok(sourceMap); 344} 345 346// Does not include null for async/await with esm 347// Refs: https://github.com/nodejs/node/issues/42417 348{ 349 const output = spawnSync(process.execPath, [ 350 '--enable-source-maps', 351 require.resolve('../fixtures/source-map/throw-async.mjs'), 352 ]); 353 // Error in original context of source content: 354 assert.match( 355 output.stderr.toString(), 356 /throw new Error\(message\)\r?\n.*\^/ 357 ); 358 // Rewritten stack trace: 359 assert.match(output.stderr.toString(), /at Throw \([^)]+throw-async\.ts:4:9\)/); 360} 361 362function getSourceMapFromCache(fixtureFile, coverageDirectory) { 363 const jsonFiles = fs.readdirSync(coverageDirectory); 364 for (const jsonFile of jsonFiles) { 365 let maybeSourceMapCache; 366 try { 367 maybeSourceMapCache = require( 368 path.join(coverageDirectory, jsonFile) 369 )['source-map-cache'] || {}; 370 } catch (err) { 371 console.warn(err); 372 maybeSourceMapCache = {}; 373 } 374 const keys = Object.keys(maybeSourceMapCache); 375 for (const key of keys) { 376 if (key.includes(fixtureFile)) { 377 return maybeSourceMapCache[key]; 378 } 379 } 380 } 381} 382