test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. "use strict";
  2. /* ------------------------------------------------------------------------ */
  3. const nodeVersion = Math.floor (Number (process.version.match (/^v(\d+\.\d+)/)[1]))
  4. /* ------------------------------------------------------------------------ */
  5. require ('chai').should ()
  6. /* ------------------------------------------------------------------------ */
  7. describe ('impl/partition', () => {
  8. const partition = require ('./impl/partition')
  9. const spans = partition ([ 'a', 'b', 'c', undefined, undefined, 42], x => typeof x)
  10. spans.should.deep.equal ([ { label: 'string', items: ['a', 'b', 'c'] },
  11. { label: 'undefined', items: [undefined, undefined] },
  12. { label: 'number', items: [42] } ])
  13. })
  14. /* ------------------------------------------------------------------------ */
  15. describe ('StackTracey', () => {
  16. const path = require ('path')
  17. const StackTracey = require ('./stacktracey')
  18. StackTracey.resetCache ()
  19. const shouldBeVisibleInStackTrace = () => new StackTracey () // @hide
  20. it ('works', () => {
  21. const stack = shouldBeVisibleInStackTrace ()
  22. stack.should.be.an.instanceof (StackTracey)
  23. stack.items[0].should.deep.equal ({
  24. beforeParse: 'at shouldBeVisibleInStackTrace (' + path.join (process.cwd (), 'test.js') + ':32:47)',
  25. callee: 'shouldBeVisibleInStackTrace',
  26. index: false,
  27. native: false,
  28. file: path.join (process.cwd (), 'test.js').replace (/\\/g, '/'),
  29. line: 32,
  30. column: 47,
  31. calleeShort: 'shouldBeVisibleInStackTrace',
  32. fileName: 'test.js',
  33. fileRelative: 'test.js',
  34. fileShort: 'test.js',
  35. thirdParty: false,
  36. externalDomain: undefined
  37. })
  38. })
  39. it ('allows to read sources', () => {
  40. const stack = shouldBeVisibleInStackTrace ().withSources () // @hide
  41. stack.should.be.an.instanceof (StackTracey)
  42. stack.items[0].beforeParse.should.not.be.undefined // should preserve previous fields
  43. stack.items[0].sourceLine.should.equal (' const shouldBeVisibleInStackTrace = () => new StackTracey () ')
  44. stack.items[0].hide.should.equal (true) // reads // @hide marker
  45. stack.items[1].hide.should.equal (true) // reads // @hide marker
  46. const cleanStack = stack.clean ()
  47. cleanStack.should.be.an.instanceof (StackTracey)
  48. StackTracey.locationsEqual (cleanStack.items[0], stack.items[0]).should.equal (true) // should not clean top element
  49. StackTracey.locationsEqual (cleanStack.items[1], stack.items[1]).should.equal (false) // should clean second element (due to // @hide)
  50. })
  51. it ('allows to read sources (async)', () => {
  52. return shouldBeVisibleInStackTrace ().withSourcesAsync ().then (stack => { // @hide
  53. stack.should.be.an.instanceof (StackTracey)
  54. stack.items[0].beforeParse.should.not.be.undefined // should preserve previous fields
  55. stack.items[0].sourceLine.should.equal (' const shouldBeVisibleInStackTrace = () => new StackTracey () ')
  56. stack.items[0].hide.should.equal (true) // reads // @hide marker
  57. stack.items[1].hide.should.equal (true) // reads // @hide marker
  58. return stack.cleanAsync ().then (cleanStack => {
  59. cleanStack.should.be.an.instanceof (StackTracey)
  60. StackTracey.locationsEqual (cleanStack.items[0], stack.items[0]).should.equal (true) // should not clean top element
  61. StackTracey.locationsEqual (cleanStack.items[1], stack.items[1]).should.equal (false) // should clean second element (due to // @hide)
  62. })
  63. })
  64. })
  65. it ('allows creation from array + groups duplicate lines', () => {
  66. const stack = new StackTracey ([
  67. { file: 'yo.js', line: 11, callee: 'a.funkktion', calleeShort: 'a' },
  68. { file: 'yo.js', line: 10, callee: 'foobar.boobar', calleeShort: 'foobar' },
  69. { file: 'yo.js', line: 10, callee: 'foobar.boobar', calleeShort: 'foobar' },
  70. { file: 'lol.js', line: 10, callee: '', calleeShort: '' },
  71. ])
  72. const clean = stack.clean ().map (x => Object.assign ({
  73. file: x.file,
  74. line: x.line,
  75. callee: x.callee,
  76. calleeShort: x.calleeShort }))
  77. clean.should.be.an.instanceof (StackTracey)
  78. clean.items.should.deep.equal ([ // .should does not recognize StackTracey as normal array...
  79. { file: 'yo.js', line: 11, callee: 'a.funkktion', calleeShort: 'a' },
  80. { file: 'yo.js', line: 10, callee: 'foobar.boobar → foobar.boobar', calleeShort: 'foobar → foobar' },
  81. { file: 'lol.js', line: 10, callee: '', calleeShort: '' },
  82. ])
  83. })
  84. it ('handles inaccessible files', () => {
  85. const stack = shouldBeVisibleInStackTrace ()
  86. stack.items[0].file = '^___^'
  87. stack.withSources ().items[0].sourceLine.should.equal ('')
  88. stack.withSourceAt (0).error.should.be.an.instanceof (Error)
  89. })
  90. it ('exposes some Array methods', () => {
  91. const stack = shouldBeVisibleInStackTrace ()
  92. const sliced = stack.slice (1)
  93. const deltaLength = (stack.items.length - sliced.items.length)
  94. deltaLength.should.equal (1)
  95. sliced.should.be.an.instanceof (StackTracey)
  96. sliced.filter (x => true).should.be.an.instanceof (StackTracey)
  97. })
  98. it ('works with sourcemaps', () => {
  99. const mkay = require ('./test_files/mkay.uglified')
  100. try {
  101. mkay ()
  102. }
  103. catch (e) {
  104. e.message.should.equal ('mkay')
  105. const top = new StackTracey (e).withSourceAt (0)
  106. top.line .should.equal (4)
  107. top.sourceLine .should.equal ('\t\t\t\t\tthrow new Error (\'mkay\') }')
  108. top.file .should.equal (path.resolve ('./test_files/mkay.js').replace (/\\/g, '/'))
  109. top.fileShort .should.equal ('test_files/mkay.js')
  110. top.fileName .should.equal ('mkay.js')
  111. }
  112. })
  113. it ('pretty printing works', function prettyTest () {
  114. const pretty = new StackTracey ().clean ().asTable ()
  115. console.log ('')
  116. console.log (pretty, '\n')
  117. //const spaces = nodeVersion > 8 ? ' ' : ' ';
  118. console.log (pretty.split ('\n')[0].match (/at prettyTest\s+test.js\:144\s+const pretty = new StackTracey \(\)\.clean\.pretty/))
  119. })
  120. it ('trims too long columns in the pretty printed output', () => {
  121. const stack = new StackTracey ([
  122. { fileShort: 'dasdasdasdadadadasdasdasdasdasddasdsadadasdassdasdaddadasdas.js', line: 11, calleeShort: 'dadasdasdasdasdasdasdasdasdasdasdasdasd' },
  123. ])
  124. stack.withSources ().asTable ().split ('\n')[0].should.equal ('at dadasdasdasdasdasdasdasdasdas… …asdadadadasdasdasdasdasddasdsadadasdassdasdaddadasdas.js:11 ')
  125. })
  126. it ('exposes Array methods', () => {
  127. const stack = new StackTracey ([
  128. { file: 'foo' },
  129. { file: 'bar' }
  130. ])
  131. const mapped = stack.map ((x, i) => Object.assign (x, { i }))
  132. mapped.items.should.deep.equal ([ { file: 'foo', i: 0 }, { file: 'bar', i: 1 } ])
  133. mapped.should.be.an.instanceof (StackTracey)
  134. const filtered = stack.filter (x => x.file === 'bar')
  135. filtered.items.length.should.equal (1)
  136. filtered.items[0].should.deep.equal ({ file: 'bar', i: 1 })
  137. })
  138. it ('computes relative path correctly', () => {
  139. StackTracey.prototype.decomposePath ('webpack:///~/jquery/dist/jquery.js')
  140. .should.deep.equal ( ['~/jquery/dist/jquery.js', undefined])
  141. StackTracey.prototype.decomposePath ('webpack:/webpack/bootstrap')
  142. .should.deep.equal ( ['webpack/bootstrap', undefined])
  143. })
  144. it ('computes short path correctly', () => {
  145. StackTracey.prototype.shortenPath ('webpack/bootstrap/jquery/dist/jquery.js')
  146. .should.equal ('jquery/dist/jquery.js')
  147. StackTracey.prototype.shortenPath ('node_modules/jquery/dist/jquery.js')
  148. .should.equal ('jquery/dist/jquery.js')
  149. })
  150. if (nodeVersion >= 5) {
  151. it ('recognizes SyntaxErrors', () => {
  152. try { require ('./test_files/syntax_error.js') }
  153. catch (e) {
  154. const stack = new StackTracey (e).clean ()
  155. console.log ('')
  156. console.log (stack.asTable (), '\n')
  157. stack.items[0].syntaxError.should.equal (true)
  158. stack.items[0].column.should.equal (5)
  159. const spaces = nodeVersion > 8 ? ' ' : ' '
  160. const spaces2 = nodeVersion > 8 ? (nodeVersion > 11 ? ' ' : ' ') : ' '
  161. stack.asTable ().split ('\n')[0].should.equal ('at (syntax error)' + spaces + 'test_files/syntax_error.js:2' + spaces2 + 'foo->bar () ')
  162. }
  163. })
  164. }
  165. it ('allows to override isThirdParty()', () => {
  166. class MyStackTracey extends StackTracey {
  167. isThirdParty (path) {
  168. return super.isThirdParty (path) || (path === 'test.js')
  169. }
  170. }
  171. new MyStackTracey ().items[0].thirdParty.should.equal (true)
  172. class MyStackTracey2 extends MyStackTracey {
  173. isThirdParty (path) {
  174. return super.isThirdParty (path) && (path !== 'test.js')
  175. }
  176. }
  177. new MyStackTracey2 ().items[0].thirdParty.should.equal (false)
  178. })
  179. it ('.withSourceAt', () => {
  180. const line = new StackTracey ().withSourceAt (0).sourceLine.trim ()
  181. line.should.equal ('const line = new StackTracey ().withSourceAt (0).sourceLine.trim ()')
  182. })
  183. it ('.at', () => {
  184. new StackTracey ().at (0).file.includes ('stacktracey/test.js').should.equal (true)
  185. })
  186. it ('detects Array methods as native', () => {
  187. const arr = [1,2,3]
  188. const stack = arr.reduce (() => new StackTracey ())
  189. stack.items[1].native.should.equal (true)
  190. })
  191. it ('works on Windows', () => {
  192. const dir = process.cwd ()
  193. const windowsStack =
  194. [
  195. 'Error',
  196. ' at Context.it (' + dir + '\\test.js:38:22)',
  197. ' at callFn (' + dir + '\\node_modules\\mocha\\lib\\runnable.js:354:21)',
  198. ' at runCallback (timers.js:800:20)'
  199. ].join ('\n')
  200. const stack = new StackTracey (windowsStack)
  201. const pretty = stack.withSources ().asTable ()
  202. const lines = pretty.split ('\n')
  203. console.log ('')
  204. console.log (pretty, '\n')
  205. lines[0].should.equal ('at it test.js:38 stack.should.be.an.instanceof (StackTracey)')
  206. lines[1].indexOf ('at callFn mocha/lib/runnable.js:354').should.equal (0)
  207. })
  208. it ('parses "eval at" stuff', () => {
  209. function bar() {
  210. const entry = new StackTracey().items[1]
  211. entry.callee.should.equal('eval')
  212. entry.fileName.should.equal('test.js')
  213. }
  214. function foo() {
  215. eval('bar()')
  216. }
  217. foo()
  218. })
  219. it ('recognizes externalDomain', () => {
  220. const stack =
  221. [
  222. 'Error',
  223. ' at foo (test.js:38:22)',
  224. ' at bar (http://shmoogle.google.com/hohoho/test.js:38:22)',
  225. ].join ('\n')
  226. const items = new StackTracey(stack).items
  227. ;(items[0].externalDomain === undefined).should.be.true
  228. items[1].externalDomain.should.equal('shmoogle.google.com')
  229. items[0].thirdParty.should.be.false
  230. items[1].thirdParty.should.be.true
  231. items[1].fileRelative.should.equal('hohoho/test.js')
  232. })
  233. it ('recognizes locations without column', () => {
  234. const stack = [
  235. 'Error',
  236. ' at ValidateCompatibilityWithBindGroupLayout (../../third_party/dawn/src/dawn_native/ShaderModule.cpp:395)',
  237. ].join ('\n')
  238. const items = new StackTracey(stack).items
  239. items[0].should.contain({
  240. callee: 'ValidateCompatibilityWithBindGroupLayout',
  241. calleeShort: 'ValidateCompatibilityWithBindGroupLayout',
  242. fileRelative: '../../third_party/dawn/src/dawn_native/ShaderModule.cpp',
  243. fileShort: '../../third_party/dawn/src/dawn_native/ShaderModule.cpp',
  244. fileName: 'ShaderModule.cpp'
  245. })
  246. })
  247. })