• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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