node/test/parallel/test-source-map-enable.js
77e3dfca5c
lib: don't parse windows drive letters as schemes
We were incorrectly parsing windows drive letters as schemes. This was
polluting the source map cache with malformed file paths.

Fixes: https://github.com/nodejs/node/issues/50523
PR-URL: https://github.com/nodejs/node/pull/50580
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2023-12-05 09:24:14 +00:00

406 lines
13 KiB
JavaScript

'use strict';
if (!process.features.inspector) return;
const common = require('../common');
const assert = require('assert');
const { dirname } = require('path');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { pathToFileURL } = require('url');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
let dirc = 0;
function nextdir() {
return process.env.NODE_V8_COVERAGE ||
tmpdir.resolve(`source_map_${++dirc}`);
}
// Outputs source maps when event loop is drained, with no async logic.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/basic'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
if (output.status !== 0) {
console.log(output.stderr.toString());
}
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('basic.js', coverageDirectory);
assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/418');
}
// Outputs source maps when process.kill(process.pid, "SIGINT"); exits process.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/sigint'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
if (!common.isWindows) {
if (output.signal !== 'SIGINT') {
console.log(output.stderr.toString());
}
assert.strictEqual(output.signal, 'SIGINT');
}
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('sigint.js', coverageDirectory);
assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/402');
}
// Outputs source maps when source-file calls process.exit(1).
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/exit-1'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('exit-1.js', coverageDirectory);
assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/404');
}
// Outputs source-maps for esm module.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
'--no-warnings',
require.resolve('../fixtures/source-map/esm-basic.mjs'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache('esm-basic.mjs', coverageDirectory);
assert.strictEqual(sourceMap.url, 'https://ci.nodejs.org/405');
}
// Loads source-maps with relative path from .map file on disk.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/disk-relative-path'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache(
'disk-relative-path.js',
coverageDirectory
);
// Source-map should have been loaded from disk and sources should have been
// rewritten, such that they're absolute paths.
assert.strictEqual(
dirname(pathToFileURL(
require.resolve('../fixtures/source-map/disk-relative-path')).href),
dirname(sourceMap.data.sources[0])
);
}
// Loads source-maps from inline data URL.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/inline-base64.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache(
'inline-base64.js',
coverageDirectory
);
// base64 JSON should have been decoded, and paths to sources should have
// been rewritten such that they're absolute:
assert.strictEqual(
dirname(pathToFileURL(
require.resolve('../fixtures/source-map/inline-base64')).href),
dirname(sourceMap.data.sources[0])
);
}
// base64 encoding error does not crash application.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/inline-base64-type-error.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache(
'inline-base64-type-error.js',
coverageDirectory
);
assert.strictEqual(sourceMap.data, null);
}
// JSON error does not crash application.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/inline-base64-json-error.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache(
'inline-base64-json-error.js',
coverageDirectory
);
assert.strictEqual(sourceMap.data, null);
}
// Does not apply source-map to stack trace if --experimental-modules
// is not set.
{
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/uglify-throw.js'),
]);
assert.strictEqual(
output.stderr.toString().match(/.*uglify-throw-original\.js:5:9/),
null
);
assert.strictEqual(
output.stderr.toString().match(/.*uglify-throw-original\.js:9:3/),
null
);
}
// Applies source-maps generated by uglifyjs to stack trace.
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/uglify-throw.js'),
]);
assert.match(
output.stderr.toString(),
/.*uglify-throw-original\.js:5:9/
);
assert.match(
output.stderr.toString(),
/.*uglify-throw-original\.js:9:3/
);
assert.match(output.stderr.toString(), /at Hello/);
}
// Applies source-maps generated by tsc to stack trace.
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/typescript-throw.js'),
]);
assert.ok(output.stderr.toString().match(/.*typescript-throw\.ts:18:11/));
assert.ok(output.stderr.toString().match(/.*typescript-throw\.ts:24:1/));
}
// Applies source-maps generated by babel to stack trace.
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/babel-throw.js'),
]);
assert.ok(
output.stderr.toString().match(/.*babel-throw-original\.js:18:31/)
);
}
// Applies source-maps generated by nyc to stack trace.
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/istanbul-throw.js'),
]);
assert.ok(
output.stderr.toString().match(/.*istanbul-throw-original\.js:5:9/)
);
assert.ok(
output.stderr.toString().match(/.*istanbul-throw-original\.js:9:3/)
);
}
// Applies source-maps in esm modules to stack trace.
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/babel-esm.mjs'),
]);
assert.ok(
output.stderr.toString().match(/.*babel-esm-original\.mjs:9:29/)
);
}
// Does not persist url parameter if source-map has been parsed.
{
const coverageDirectory = nextdir();
spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/inline-base64.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
const sourceMap = getSourceMapFromCache(
'inline-base64.js',
coverageDirectory
);
assert.strictEqual(sourceMap.url, null);
}
// Persists line lengths for in-memory representation of source file.
{
const coverageDirectory = nextdir();
spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/istanbul-throw.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
const sourceMap = getSourceMapFromCache(
'istanbul-throw.js',
coverageDirectory
);
if (common.checkoutEOL === '\r\n') {
assert.deepStrictEqual(sourceMap.lineLengths, [1086, 31, 185, 649, 0]);
} else {
assert.deepStrictEqual(sourceMap.lineLengths, [1085, 30, 184, 648, 0]);
}
}
// trace.length === 0 .
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/emptyStackError.js'),
]);
assert.ok(
output.stderr.toString().match('emptyStackError')
);
}
// Does not attempt to apply path resolution logic to absolute URLs
// with schemes.
// Refs: https://github.com/webpack/webpack/issues/9601
// Refs: https://sourcemaps.info/spec.html#h.75yo6yoyk7x5
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/webpack.js'),
]);
// Error in original context of source content:
assert.match(
output.stderr.toString(),
/throw new Error\('oh no!'\)\r?\n.*\^/
);
// Rewritten stack trace:
assert.match(output.stderr.toString(), /webpack:\/\/\/webpack\.js:14:9/);
assert.match(output.stderr.toString(), /at functionD.*14:9/);
assert.match(output.stderr.toString(), /at functionC.*10:3/);
}
// Properly converts Windows absolute paths to absolute URLs.
// Refs: https://github.com/nodejs/node/issues/50523
// Refs: https://github.com/TypeStrong/ts-node/issues/1769
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
require.resolve('../fixtures/source-map/ts-node-win32.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
assert.strictEqual(output.status, 0);
assert.strictEqual(output.stderr.toString(), '');
const sourceMap = getSourceMapFromCache(
'ts-node-win32.js',
coverageDirectory
);
// base64 JSON should have been decoded, the D: in the sources field should
// have been taken as the drive letter on Windows, the scheme on POSIX.
assert.strictEqual(
sourceMap.data.sources[0],
common.isWindows ?
'file:///D:/workspaces/node/test/fixtures/source-map/ts-node.ts' :
'd:/workspaces/node/test/fixtures/source-map/ts-node.ts'
);
}
// Stores and applies source map associated with file that throws while
// being required.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/throw-on-require-entry.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
const sourceMap = getSourceMapFromCache(
'throw-on-require.js',
coverageDirectory
);
// Rewritten stack trace.
assert.match(output.stderr.toString(), /throw-on-require\.ts:9:9/);
// Source map should have been serialized.
assert.ok(sourceMap);
}
// Does not throw TypeError when primitive value is thrown.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/throw-string.js'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
const sourceMap = getSourceMapFromCache(
'throw-string.js',
coverageDirectory
);
// Original stack trace.
assert.match(output.stderr.toString(), /goodbye/);
// Source map should have been serialized.
assert.ok(sourceMap);
}
// Does not throw TypeError when exception occurs as result of missing named
// export.
{
const coverageDirectory = nextdir();
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/esm-export-missing.mjs'),
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
const sourceMap = getSourceMapFromCache(
'esm-export-missing.mjs',
coverageDirectory
);
// Module loader error displayed.
assert.match(output.stderr.toString(),
/does not provide an export named 'Something'/);
// Source map should have been serialized.
assert.ok(sourceMap);
}
// Does not include null for async/await with esm
// Refs: https://github.com/nodejs/node/issues/42417
{
const output = spawnSync(process.execPath, [
'--enable-source-maps',
require.resolve('../fixtures/source-map/throw-async.mjs'),
]);
// Error in original context of source content:
assert.match(
output.stderr.toString(),
/throw new Error\(message\)\r?\n.*\^/
);
// Rewritten stack trace:
assert.match(output.stderr.toString(), /at Throw \([^)]+throw-async\.ts:4:9\)/);
}
function getSourceMapFromCache(fixtureFile, coverageDirectory) {
const jsonFiles = fs.readdirSync(coverageDirectory);
for (const jsonFile of jsonFiles) {
let maybeSourceMapCache;
try {
maybeSourceMapCache = require(
path.join(coverageDirectory, jsonFile)
)['source-map-cache'] || {};
} catch (err) {
console.warn(err);
maybeSourceMapCache = {};
}
const keys = Object.keys(maybeSourceMapCache);
for (const key of keys) {
if (key.includes(fixtureFile)) {
return maybeSourceMapCache[key];
}
}
}
}