123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- require('./source-map-support').install({
- emptyCacheBetweenOperations: true // Needed to be able to test for failure
- });
- var SourceMapGenerator = require('source-map').SourceMapGenerator;
- var child_process = require('child_process');
- var assert = require('assert');
- var fs = require('fs');
- function compareLines(actual, expected) {
- assert(actual.length >= expected.length, 'got ' + actual.length + ' lines but expected at least ' + expected.length + ' lines');
- for (var i = 0; i < expected.length; i++) {
- // Some tests are regular expressions because the output format changed slightly between node v0.9.2 and v0.9.3
- if (expected[i] instanceof RegExp) {
- assert(expected[i].test(actual[i]), JSON.stringify(actual[i]) + ' does not match ' + expected[i]);
- } else {
- assert.equal(actual[i], expected[i]);
- }
- }
- }
- function createEmptySourceMap() {
- return new SourceMapGenerator({
- file: '.generated.js',
- sourceRoot: '.'
- });
- }
- function createSourceMapWithGap() {
- var sourceMap = createEmptySourceMap();
- sourceMap.addMapping({
- generated: { line: 100, column: 0 },
- original: { line: 100, column: 0 },
- source: '.original.js'
- });
- return sourceMap;
- }
- function createSingleLineSourceMap() {
- var sourceMap = createEmptySourceMap();
- sourceMap.addMapping({
- generated: { line: 1, column: 0 },
- original: { line: 1, column: 0 },
- source: '.original.js'
- });
- return sourceMap;
- }
- function createSecondLineSourceMap() {
- var sourceMap = createEmptySourceMap();
- sourceMap.addMapping({
- generated: { line: 2, column: 0 },
- original: { line: 1, column: 0 },
- source: '.original.js'
- });
- return sourceMap;
- }
- function createMultiLineSourceMap() {
- var sourceMap = createEmptySourceMap();
- for (var i = 1; i <= 100; i++) {
- sourceMap.addMapping({
- generated: { line: i, column: 0 },
- original: { line: 1000 + i, column: 99 + i },
- source: 'line' + i + '.js'
- });
- }
- return sourceMap;
- }
- function createMultiLineSourceMapWithSourcesContent() {
- var sourceMap = createEmptySourceMap();
- var original = new Array(1001).join('\n');
- for (var i = 1; i <= 100; i++) {
- sourceMap.addMapping({
- generated: { line: i, column: 0 },
- original: { line: 1000 + i, column: 4 },
- source: 'original.js'
- });
- original += ' line ' + i + '\n';
- }
- sourceMap.setSourceContent('original.js', original);
- return sourceMap;
- }
- function compareStackTrace(sourceMap, source, expected) {
- // Check once with a separate source map
- fs.writeFileSync('.generated.js.map', sourceMap);
- fs.writeFileSync('.generated.js', 'exports.test = function() {' +
- source.join('\n') + '};//@ sourceMappingURL=.generated.js.map');
- try {
- delete require.cache[require.resolve('./.generated')];
- require('./.generated').test();
- } catch (e) {
- compareLines(e.stack.split(/\r\n|\n/), expected);
- }
- fs.unlinkSync('.generated.js');
- fs.unlinkSync('.generated.js.map');
- // Check again with an inline source map (in a data URL)
- fs.writeFileSync('.generated.js', 'exports.test = function() {' +
- source.join('\n') + '};//@ sourceMappingURL=data:application/json;base64,' +
- new Buffer(sourceMap.toString()).toString('base64'));
- try {
- delete require.cache[require.resolve('./.generated')];
- require('./.generated').test();
- } catch (e) {
- compareLines(e.stack.split(/\r\n|\n/), expected);
- }
- fs.unlinkSync('.generated.js');
- }
- function compareStdout(done, sourceMap, source, expected) {
- fs.writeFileSync('.original.js', 'this is the original code');
- fs.writeFileSync('.generated.js.map', sourceMap);
- fs.writeFileSync('.generated.js', source.join('\n') +
- '//@ sourceMappingURL=.generated.js.map');
- child_process.exec('node ./.generated', function(error, stdout, stderr) {
- try {
- compareLines(
- (stdout + stderr)
- .trim()
- .split(/\r\n|\n/)
- .filter(function (line) { return line !== '' }), // Empty lines are not relevant.
- expected
- );
- } catch (e) {
- return done(e);
- }
- fs.unlinkSync('.generated.js');
- fs.unlinkSync('.generated.js.map');
- fs.unlinkSync('.original.js');
- done();
- });
- }
- it('normal throw', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'throw new Error("test");'
- ], [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ]);
- });
- it('throw inside function', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'function foo() {',
- ' throw new Error("test");',
- '}',
- 'foo();'
- ], [
- 'Error: test',
- /^ at foo \((?:.*[/\\])?line2\.js:1002:102\)$/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line4\.js:1004:104\)$/
- ]);
- });
- it('throw inside function inside function', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'function foo() {',
- ' function bar() {',
- ' throw new Error("test");',
- ' }',
- ' bar();',
- '}',
- 'foo();'
- ], [
- 'Error: test',
- /^ at bar \((?:.*[/\\])?line3\.js:1003:103\)$/,
- /^ at foo \((?:.*[/\\])?line5\.js:1005:105\)$/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line7\.js:1007:107\)$/
- ]);
- });
- it('eval', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'eval("throw new Error(\'test\')");'
- ], [
- 'Error: test',
- // Before Node 4, `Object.eval`, after just `eval`.
- /^ at (?:Object\.)?eval \(eval at (<anonymous>|exports.test) \((?:.*[/\\])?line1\.js:1001:101\)/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ]);
- });
- it('eval inside eval', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'eval("eval(\'throw new Error(\\"test\\")\')");'
- ], [
- 'Error: test',
- /^ at (?:Object\.)?eval \(eval at (<anonymous>|exports.test) \(eval at (<anonymous>|exports.test) \((?:.*[/\\])?line1\.js:1001:101\)/,
- /^ at (?:Object\.)?eval \(eval at (<anonymous>|exports.test) \((?:.*[/\\])?line1\.js:1001:101\)/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ]);
- });
- it('eval inside function', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'function foo() {',
- ' eval("throw new Error(\'test\')");',
- '}',
- 'foo();'
- ], [
- 'Error: test',
- /^ at eval \(eval at foo \((?:.*[/\\])?line2\.js:1002:102\)/,
- /^ at foo \((?:.*[/\\])?line2\.js:1002:102\)/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line4\.js:1004:104\)$/
- ]);
- });
- it('eval with sourceURL', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'eval("throw new Error(\'test\')//@ sourceURL=sourceURL.js");'
- ], [
- 'Error: test',
- /^ at (?:Object\.)?eval \(sourceURL\.js:1:7\)$/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ]);
- });
- it('eval with sourceURL inside eval', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'eval("eval(\'throw new Error(\\"test\\")//@ sourceURL=sourceURL.js\')");'
- ], [
- 'Error: test',
- /^ at (?:Object\.)?eval \(sourceURL\.js:1:7\)$/,
- /^ at (?:Object\.)?eval \(eval at (<anonymous>|exports.test) \((?:.*[/\\])?line1\.js:1001:101\)/,
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ]);
- });
- it('native function', function() {
- compareStackTrace(createSingleLineSourceMap(), [
- '[1].map(function(x) { throw new Error(x); });'
- ], [
- 'Error: 1',
- /[/\\].original\.js/,
- /at Array\.map \(native\)/
- ]);
- });
- it('function constructor', function() {
- compareStackTrace(createMultiLineSourceMap(), [
- 'throw new Function(")");'
- ], [
- 'SyntaxError: Unexpected token )',
- ]);
- });
- it('throw with empty source map', function() {
- compareStackTrace(createEmptySourceMap(), [
- 'throw new Error("test");'
- ], [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?.generated.js:1:34\)$/
- ]);
- });
- it('throw in Timeout with empty source map', function(done) {
- compareStdout(done, createEmptySourceMap(), [
- 'require("./source-map-support").install();',
- 'setTimeout(function () {',
- ' throw new Error("this is the error")',
- '})'
- ], [
- /[/\\].generated.js:3$/,
- ' throw new Error("this is the error")',
- /^ \^$/,
- 'Error: this is the error',
- /^ at ((null)|(Timeout))\._onTimeout \((?:.*[/\\])?.generated.js:3:11\)$/
- ]);
- });
- it('throw with source map with gap', function() {
- compareStackTrace(createSourceMapWithGap(), [
- 'throw new Error("test");'
- ], [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?.generated.js:1:34\)$/
- ]);
- });
- it('sourcesContent with data URL', function() {
- compareStackTrace(createMultiLineSourceMapWithSourcesContent(), [
- 'throw new Error("test");'
- ], [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?original.js:1001:5\)$/
- ]);
- });
- it('finds the last sourceMappingURL', function() {
- compareStackTrace(createMultiLineSourceMapWithSourcesContent(), [
- '//# sourceMappingURL=missing.map.js', // NB: compareStackTrace adds another source mapping.
- 'throw new Error("test");'
- ], [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?original.js:1002:5\)$/
- ]);
- });
- it('default options', function(done) {
- compareStdout(done, createSecondLineSourceMap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install();',
- 'process.nextTick(foo);',
- 'process.nextTick(function() { process.exit(1); });'
- ], [
- /[/\\].original\.js:1$/,
- 'this is the original code',
- '^',
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.original\.js:1:1\)$/
- ]);
- });
- it('handleUncaughtExceptions is true', function(done) {
- compareStdout(done, createSecondLineSourceMap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install({ handleUncaughtExceptions: true });',
- 'process.nextTick(foo);'
- ], [
- /[/\\].original\.js:1$/,
- 'this is the original code',
- '^',
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.original\.js:1:1\)$/
- ]);
- });
- it('handleUncaughtExceptions is false', function(done) {
- compareStdout(done, createSecondLineSourceMap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install({ handleUncaughtExceptions: false });',
- 'process.nextTick(foo);'
- ], [
- /[/\\].generated.js:2$/,
- 'function foo() { throw new Error("this is the error"); }',
- // Before Node 4, the arrow points on the `new`, after on the
- // `throw`.
- /^ (?: )?\^$/,
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.original\.js:1:1\)$/
- ]);
- });
- it('default options with empty source map', function(done) {
- compareStdout(done, createEmptySourceMap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install();',
- 'process.nextTick(foo);'
- ], [
- /[/\\].generated.js:2$/,
- 'function foo() { throw new Error("this is the error"); }',
- /^ (?: )?\^$/,
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.generated.js:2:24\)$/
- ]);
- });
- it('default options with source map with gap', function(done) {
- compareStdout(done, createSourceMapWithGap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install();',
- 'process.nextTick(foo);'
- ], [
- /[/\\].generated.js:2$/,
- 'function foo() { throw new Error("this is the error"); }',
- /^ (?: )?\^$/,
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.generated.js:2:24\)$/
- ]);
- });
- it('specifically requested error source', function(done) {
- compareStdout(done, createSecondLineSourceMap(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'var sms = require("./source-map-support");',
- 'sms.install({ handleUncaughtExceptions: false });',
- 'process.on("uncaughtException", function (e) { console.log("SRC:" + sms.getErrorSource(e)); });',
- 'process.nextTick(foo);'
- ], [
- /^SRC:.*[/\\].original.js:1$/,
- 'this is the original code',
- '^'
- ]);
- });
- it('sourcesContent', function(done) {
- compareStdout(done, createMultiLineSourceMapWithSourcesContent(), [
- '',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install();',
- 'process.nextTick(foo);',
- 'process.nextTick(function() { process.exit(1); });'
- ], [
- /[/\\]original\.js:1002$/,
- ' line 2',
- ' ^',
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?original\.js:1002:5\)$/
- ]);
- });
- it('missing source maps should also be cached', function(done) {
- compareStdout(done, createSingleLineSourceMap(), [
- '',
- 'var count = 0;',
- 'function foo() {',
- ' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
- '}',
- 'require("./source-map-support").install({',
- ' overrideRetrieveSourceMap: true,',
- ' retrieveSourceMap: function(name) {',
- ' if (/\\.generated.js$/.test(name)) count++;',
- ' return null;',
- ' }',
- '});',
- 'process.nextTick(foo);',
- 'process.nextTick(foo);',
- 'process.nextTick(function() { console.log(count); });',
- ], [
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.generated.js:4:15\)$/,
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?.generated.js:4:15\)$/,
- '1', // The retrieval should only be attempted once
- ]);
- });
- it('should consult all retrieve source map providers', function(done) {
- compareStdout(done, createSingleLineSourceMap(), [
- '',
- 'var count = 0;',
- 'function foo() {',
- ' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
- '}',
- 'require("./source-map-support").install({',
- ' retrieveSourceMap: function(name) {',
- ' if (/\\.generated.js$/.test(name)) count++;',
- ' return undefined;',
- ' }',
- '});',
- 'require("./source-map-support").install({',
- ' retrieveSourceMap: function(name) {',
- ' if (/\\.generated.js$/.test(name)) {',
- ' count++;',
- ' return ' + JSON.stringify({url: '.original.js', map: createMultiLineSourceMapWithSourcesContent().toJSON()}) + ';',
- ' }',
- ' }',
- '});',
- 'process.nextTick(foo);',
- 'process.nextTick(foo);',
- 'process.nextTick(function() { console.log(count); });',
- ], [
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?original.js:1004:5\)$/,
- 'Error: this is the error',
- /^ at foo \((?:.*[/\\])?original.js:1004:5\)$/,
- '1', // The retrieval should only be attempted once
- ]);
- });
- it('should allow for runtime inline source maps', function(done) {
- var sourceMap = createMultiLineSourceMapWithSourcesContent();
- fs.writeFileSync('.generated.jss', 'foo');
- compareStdout(function(err) {
- fs.unlinkSync('.generated.jss');
- done(err);
- }, createSingleLineSourceMap(), [
- 'require("./source-map-support").install({',
- ' hookRequire: true',
- '});',
- 'require.extensions[".jss"] = function(module, filename) {',
- ' module._compile(',
- JSON.stringify([
- '',
- 'var count = 0;',
- 'function foo() {',
- ' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
- '}',
- 'process.nextTick(foo);',
- 'process.nextTick(foo);',
- 'process.nextTick(function() { console.log(count); });',
- '//@ sourceMappingURL=data:application/json;charset=utf8;base64,' + new Buffer(sourceMap.toString()).toString('base64')
- ].join('\n')),
- ', filename);',
- '};',
- 'require("./.generated.jss");',
- ], [
- 'Error: this is the error',
- /^ at foo \(.*[/\\]original.js:1004:5\)$/,
- 'Error: this is the error',
- /^ at foo \(.*[/\\]original.js:1004:5\)$/,
- '0', // The retrieval should only be attempted once
- ]);
- });
- /* The following test duplicates some of the code in
- * `compareStackTrace` but appends a charset to the
- * source mapping url.
- */
- it('finds source maps with charset specified', function() {
- var sourceMap = createMultiLineSourceMap()
- var source = [ 'throw new Error("test");' ];
- var expected = [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ];
- fs.writeFileSync('.generated.js', 'exports.test = function() {' +
- source.join('\n') + '};//@ sourceMappingURL=data:application/json;charset=utf8;base64,' +
- new Buffer(sourceMap.toString()).toString('base64'));
- try {
- delete require.cache[require.resolve('./.generated')];
- require('./.generated').test();
- } catch (e) {
- compareLines(e.stack.split(/\r\n|\n/), expected);
- }
- fs.unlinkSync('.generated.js');
- });
- /* The following test duplicates some of the code in
- * `compareStackTrace` but appends some code and a
- * comment to the source mapping url.
- */
- it('allows code/comments after sourceMappingURL', function() {
- var sourceMap = createMultiLineSourceMap()
- var source = [ 'throw new Error("test");' ];
- var expected = [
- 'Error: test',
- /^ at Object\.exports\.test \((?:.*[/\\])?line1\.js:1001:101\)$/
- ];
- fs.writeFileSync('.generated.js', 'exports.test = function() {' +
- source.join('\n') + '};//# sourceMappingURL=data:application/json;base64,' +
- new Buffer(sourceMap.toString()).toString('base64') +
- '\n// Some comment below the sourceMappingURL\nvar foo = 0;');
- try {
- delete require.cache[require.resolve('./.generated')];
- require('./.generated').test();
- } catch (e) {
- compareLines(e.stack.split(/\r\n|\n/), expected);
- }
- fs.unlinkSync('.generated.js');
- });
- it('handleUncaughtExceptions is true with existing listener', function(done) {
- var source = [
- 'process.on("uncaughtException", function() { /* Silent */ });',
- 'function foo() { throw new Error("this is the error"); }',
- 'require("./source-map-support").install();',
- 'process.nextTick(foo);',
- '//@ sourceMappingURL=.generated.js.map'
- ];
- fs.writeFileSync('.original.js', 'this is the original code');
- fs.writeFileSync('.generated.js.map', createSingleLineSourceMap());
- fs.writeFileSync('.generated.js', source.join('\n'));
- child_process.exec('node ./.generated', function(error, stdout, stderr) {
- fs.unlinkSync('.generated.js');
- fs.unlinkSync('.generated.js.map');
- fs.unlinkSync('.original.js');
- assert.equal((stdout + stderr).trim(), '');
- done();
- });
- });
|