test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. require('./source-map-support').install({
  2. emptyCacheBetweenOperations: true // Needed to be able to test for failure
  3. });
  4. var SourceMapGenerator = require('source-map').SourceMapGenerator;
  5. var child_process = require('child_process');
  6. var assert = require('assert');
  7. var fs = require('fs');
  8. function compareLines(actual, expected) {
  9. assert(actual.length >= expected.length, 'got ' + actual.length + ' lines but expected at least ' + expected.length + ' lines');
  10. for (var i = 0; i < expected.length; i++) {
  11. // Some tests are regular expressions because the output format changed slightly between node v0.9.2 and v0.9.3
  12. if (expected[i] instanceof RegExp) {
  13. assert(expected[i].test(actual[i]), JSON.stringify(actual[i]) + ' does not match ' + expected[i]);
  14. } else {
  15. assert.equal(actual[i], expected[i]);
  16. }
  17. }
  18. }
  19. function createEmptySourceMap() {
  20. return new SourceMapGenerator({
  21. file: '.generated.js',
  22. sourceRoot: '.'
  23. });
  24. }
  25. function createSourceMapWithGap() {
  26. var sourceMap = createEmptySourceMap();
  27. sourceMap.addMapping({
  28. generated: { line: 100, column: 0 },
  29. original: { line: 100, column: 0 },
  30. source: '.original.js'
  31. });
  32. return sourceMap;
  33. }
  34. function createSingleLineSourceMap() {
  35. var sourceMap = createEmptySourceMap();
  36. sourceMap.addMapping({
  37. generated: { line: 1, column: 0 },
  38. original: { line: 1, column: 0 },
  39. source: '.original.js'
  40. });
  41. return sourceMap;
  42. }
  43. function createMultiLineSourceMap() {
  44. var sourceMap = createEmptySourceMap();
  45. for (var i = 1; i <= 100; i++) {
  46. sourceMap.addMapping({
  47. generated: { line: i, column: 0 },
  48. original: { line: 1000 + i, column: 99 + i },
  49. source: 'line' + i + '.js'
  50. });
  51. }
  52. return sourceMap;
  53. }
  54. function createMultiLineSourceMapWithSourcesContent() {
  55. var sourceMap = createEmptySourceMap();
  56. var original = new Array(1001).join('\n');
  57. for (var i = 1; i <= 100; i++) {
  58. sourceMap.addMapping({
  59. generated: { line: i, column: 0 },
  60. original: { line: 1000 + i, column: 4 },
  61. source: 'original.js'
  62. });
  63. original += ' line ' + i + '\n';
  64. }
  65. sourceMap.setSourceContent('original.js', original);
  66. return sourceMap;
  67. }
  68. function compareStackTrace(sourceMap, source, expected) {
  69. // Check once with a separate source map
  70. fs.writeFileSync('.generated.js.map', sourceMap);
  71. fs.writeFileSync('.generated.js', 'exports.test = function() {' +
  72. source.join('\n') + '};//@ sourceMappingURL=.generated.js.map');
  73. try {
  74. delete require.cache[require.resolve('./.generated')];
  75. require('./.generated').test();
  76. } catch (e) {
  77. compareLines(e.stack.split('\n'), expected);
  78. }
  79. fs.unlinkSync('.generated.js');
  80. fs.unlinkSync('.generated.js.map');
  81. // Check again with an inline source map (in a data URL)
  82. fs.writeFileSync('.generated.js', 'exports.test = function() {' +
  83. source.join('\n') + '};//@ sourceMappingURL=data:application/json;base64,' +
  84. new Buffer(sourceMap.toString()).toString('base64'));
  85. try {
  86. delete require.cache[require.resolve('./.generated')];
  87. require('./.generated').test();
  88. } catch (e) {
  89. compareLines(e.stack.split('\n'), expected);
  90. }
  91. fs.unlinkSync('.generated.js');
  92. }
  93. function compareStdout(done, sourceMap, source, expected) {
  94. fs.writeFileSync('.original.js', 'this is the original code');
  95. fs.writeFileSync('.generated.js.map', sourceMap);
  96. fs.writeFileSync('.generated.js', source.join('\n') +
  97. '//@ sourceMappingURL=.generated.js.map');
  98. child_process.exec('node ./.generated', function(error, stdout, stderr) {
  99. try {
  100. compareLines((stdout + stderr).trim().split('\n'), expected);
  101. } catch (e) {
  102. return done(e);
  103. }
  104. fs.unlinkSync('.generated.js');
  105. fs.unlinkSync('.generated.js.map');
  106. fs.unlinkSync('.original.js');
  107. done();
  108. });
  109. }
  110. it('normal throw', function() {
  111. compareStackTrace(createMultiLineSourceMap(), [
  112. 'throw new Error("test");'
  113. ], [
  114. 'Error: test',
  115. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/
  116. ]);
  117. });
  118. it('throw inside function', function() {
  119. compareStackTrace(createMultiLineSourceMap(), [
  120. 'function foo() {',
  121. ' throw new Error("test");',
  122. '}',
  123. 'foo();'
  124. ], [
  125. 'Error: test',
  126. /^ at foo \(.*\/line2\.js:1002:102\)$/,
  127. /^ at Object\.exports\.test \(.*\/line4\.js:1004:104\)$/
  128. ]);
  129. });
  130. it('throw inside function inside function', function() {
  131. compareStackTrace(createMultiLineSourceMap(), [
  132. 'function foo() {',
  133. ' function bar() {',
  134. ' throw new Error("test");',
  135. ' }',
  136. ' bar();',
  137. '}',
  138. 'foo();'
  139. ], [
  140. 'Error: test',
  141. /^ at bar \(.*\/line3\.js:1003:103\)$/,
  142. /^ at foo \(.*\/line5\.js:1005:105\)$/,
  143. /^ at Object\.exports\.test \(.*\/line7\.js:1007:107\)$/
  144. ]);
  145. });
  146. it('eval', function() {
  147. compareStackTrace(createMultiLineSourceMap(), [
  148. 'eval("throw new Error(\'test\')");'
  149. ], [
  150. 'Error: test',
  151. /^ at Object\.eval \(eval at <anonymous> \(.*\/line1\.js:1001:101\)/,
  152. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/
  153. ]);
  154. });
  155. it('eval inside eval', function() {
  156. compareStackTrace(createMultiLineSourceMap(), [
  157. 'eval("eval(\'throw new Error(\\"test\\")\')");'
  158. ], [
  159. 'Error: test',
  160. /^ at Object\.eval \(eval at <anonymous> \(eval at <anonymous> \(.*\/line1\.js:1001:101\)/,
  161. /^ at Object\.eval \(eval at <anonymous> \(.*\/line1\.js:1001:101\)/,
  162. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/
  163. ]);
  164. });
  165. it('eval inside function', function() {
  166. compareStackTrace(createMultiLineSourceMap(), [
  167. 'function foo() {',
  168. ' eval("throw new Error(\'test\')");',
  169. '}',
  170. 'foo();'
  171. ], [
  172. 'Error: test',
  173. /^ at eval \(eval at foo \(.*\/line2\.js:1002:102\)/,
  174. /^ at foo \(.*\/line2\.js:1002:102\)/,
  175. /^ at Object\.exports\.test \(.*\/line4\.js:1004:104\)$/
  176. ]);
  177. });
  178. it('eval with sourceURL', function() {
  179. compareStackTrace(createMultiLineSourceMap(), [
  180. 'eval("throw new Error(\'test\')//@ sourceURL=sourceURL.js");'
  181. ], [
  182. 'Error: test',
  183. ' at Object.eval (sourceURL.js:1:7)',
  184. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/
  185. ]);
  186. });
  187. it('eval with sourceURL inside eval', function() {
  188. compareStackTrace(createMultiLineSourceMap(), [
  189. 'eval("eval(\'throw new Error(\\"test\\")//@ sourceURL=sourceURL.js\')");'
  190. ], [
  191. 'Error: test',
  192. ' at Object.eval (sourceURL.js:1:7)',
  193. /^ at Object\.eval \(eval at <anonymous> \(.*\/line1\.js:1001:101\)/,
  194. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/
  195. ]);
  196. });
  197. it('function constructor', function() {
  198. compareStackTrace(createMultiLineSourceMap(), [
  199. 'throw new Function(")");'
  200. ], [
  201. 'SyntaxError: Unexpected token )',
  202. /^ at Object\.Function \((?:unknown source|<anonymous>)\)$/,
  203. /^ at Object\.exports\.test \(.*\/line1\.js:1001:101\)$/,
  204. ]);
  205. });
  206. it('throw with empty source map', function() {
  207. compareStackTrace(createEmptySourceMap(), [
  208. 'throw new Error("test");'
  209. ], [
  210. 'Error: test',
  211. /^ at Object\.exports\.test \(.*\/.generated.js:1:96\)$/
  212. ]);
  213. });
  214. it('throw with source map with gap', function() {
  215. compareStackTrace(createSourceMapWithGap(), [
  216. 'throw new Error("test");'
  217. ], [
  218. 'Error: test',
  219. /^ at Object\.exports\.test \(.*\/.generated.js:1:96\)$/
  220. ]);
  221. });
  222. it('sourcesContent with data URL', function() {
  223. compareStackTrace(createMultiLineSourceMapWithSourcesContent(), [
  224. 'throw new Error("test");'
  225. ], [
  226. 'Error: test',
  227. /^ at Object\.exports\.test \(.*\/original.js:1001:5\)$/
  228. ]);
  229. });
  230. it('default options', function(done) {
  231. compareStdout(done, createSingleLineSourceMap(), [
  232. '',
  233. 'function foo() { throw new Error("this is the error"); }',
  234. 'require("./source-map-support").install();',
  235. 'process.nextTick(foo);',
  236. 'process.nextTick(function() { process.exit(1); });'
  237. ], [
  238. /\/.original\.js:1$/,
  239. 'this is the original code',
  240. '^',
  241. 'Error: this is the error',
  242. /^ at foo \(.*\/.original\.js:1:1\)$/
  243. ]);
  244. });
  245. it('handleUncaughtExceptions is true', function(done) {
  246. compareStdout(done, createSingleLineSourceMap(), [
  247. '',
  248. 'function foo() { throw new Error("this is the error"); }',
  249. 'require("./source-map-support").install({ handleUncaughtExceptions: true });',
  250. 'process.nextTick(foo);'
  251. ], [
  252. /\/.original\.js:1$/,
  253. 'this is the original code',
  254. '^',
  255. 'Error: this is the error',
  256. /^ at foo \(.*\/.original\.js:1:1\)$/
  257. ]);
  258. });
  259. it('handleUncaughtExceptions is false', function(done) {
  260. compareStdout(done, createSingleLineSourceMap(), [
  261. '',
  262. 'function foo() { throw new Error("this is the error"); }',
  263. 'require("./source-map-support").install({ handleUncaughtExceptions: false });',
  264. 'process.nextTick(foo);'
  265. ], [
  266. /\/.generated.js:2$/,
  267. 'function foo() { throw new Error("this is the error"); }',
  268. ' ^',
  269. 'Error: this is the error',
  270. /^ at foo \(.*\/.original\.js:1:1\)$/
  271. ]);
  272. });
  273. it('default options with empty source map', function(done) {
  274. compareStdout(done, createEmptySourceMap(), [
  275. '',
  276. 'function foo() { throw new Error("this is the error"); }',
  277. 'require("./source-map-support").install();',
  278. 'process.nextTick(foo);'
  279. ], [
  280. /\/.generated.js:2$/,
  281. 'function foo() { throw new Error("this is the error"); }',
  282. ' ^',
  283. 'Error: this is the error',
  284. /^ at foo \(.*\/.generated.js:2:24\)$/
  285. ]);
  286. });
  287. it('default options with source map with gap', function(done) {
  288. compareStdout(done, createSourceMapWithGap(), [
  289. '',
  290. 'function foo() { throw new Error("this is the error"); }',
  291. 'require("./source-map-support").install();',
  292. 'process.nextTick(foo);'
  293. ], [
  294. /\/.generated.js:2$/,
  295. 'function foo() { throw new Error("this is the error"); }',
  296. ' ^',
  297. 'Error: this is the error',
  298. /^ at foo \(.*\/.generated.js:2:24\)$/
  299. ]);
  300. });
  301. it('specifically requested error source', function(done) {
  302. compareStdout(done, createSingleLineSourceMap(), [
  303. '',
  304. 'function foo() { throw new Error("this is the error"); }',
  305. 'var sms = require("./source-map-support");',
  306. 'sms.install({ handleUncaughtExceptions: false });',
  307. 'process.on("uncaughtException", function (e) { console.log("SRC:" + sms.getErrorSource(e)); });',
  308. 'process.nextTick(foo);'
  309. ], [
  310. 'SRC:',
  311. /\/.original.js:1$/,
  312. 'this is the original code',
  313. '^'
  314. ]);
  315. });
  316. it('sourcesContent', function(done) {
  317. compareStdout(done, createMultiLineSourceMapWithSourcesContent(), [
  318. '',
  319. 'function foo() { throw new Error("this is the error"); }',
  320. 'require("./source-map-support").install();',
  321. 'process.nextTick(foo);',
  322. 'process.nextTick(function() { process.exit(1); });'
  323. ], [
  324. /\/original\.js:1002$/,
  325. ' line 2',
  326. ' ^',
  327. 'Error: this is the error',
  328. /^ at foo \(.*\/original\.js:1002:5\)$/
  329. ]);
  330. });
  331. it('missing source maps should also be cached', function(done) {
  332. compareStdout(done, createSingleLineSourceMap(), [
  333. '',
  334. 'var count = 0;',
  335. 'function foo() {',
  336. ' console.log(new Error("this is the error").stack.split("\\n").slice(0, 2).join("\\n"));',
  337. '}',
  338. 'require("./source-map-support").install({',
  339. ' retrieveSourceMap: function(name) {',
  340. ' if (/\\.generated.js$/.test(name)) count++;',
  341. ' return null;',
  342. ' }',
  343. '});',
  344. 'process.nextTick(foo);',
  345. 'process.nextTick(foo);',
  346. 'process.nextTick(function() { console.log(count); });',
  347. ], [
  348. 'Error: this is the error',
  349. /^ at foo \(.*\/.generated.js:4:15\)$/,
  350. 'Error: this is the error',
  351. /^ at foo \(.*\/.generated.js:4:15\)$/,
  352. '1', // The retrieval should only be attempted once
  353. ]);
  354. });