threads.spec.tsx 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295
  1. import merge from 'lodash/merge';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  4. import {Threads} from 'sentry/components/events/interfaces/threads';
  5. import {displayOptions} from 'sentry/components/events/traceEventDataSection';
  6. import {EventOrGroupType} from 'sentry/types';
  7. import {EntryType, Event} from 'sentry/types/event';
  8. describe('Threads', function () {
  9. const {project, organization} = initializeOrg();
  10. describe('non native platform', function () {
  11. describe('other platform', function () {
  12. const event: Event = {
  13. id: '020eb33f6ce64ed6adc60f8993535816',
  14. groupID: '68',
  15. eventID: '020eb33f6ce64ed6adc60f8993535816',
  16. projectID: '2',
  17. size: 3481,
  18. entries: [
  19. {
  20. data: {
  21. values: [
  22. {
  23. type: 'ZeroDivisionError',
  24. value: 'divided by 0',
  25. mechanism: null,
  26. threadId: null,
  27. module: '',
  28. stacktrace: {
  29. frames: [
  30. {
  31. filename: 'puma (3.12.6) lib/puma/thread_pool.rb',
  32. absPath: 'puma (3.12.6) lib/puma/thread_pool.rb',
  33. module: null,
  34. package: null,
  35. platform: null,
  36. instructionAddr: null,
  37. symbolAddr: null,
  38. function: 'block in spawn_thread',
  39. rawFunction: null,
  40. symbol: null,
  41. context: [],
  42. lineNo: 135,
  43. colNo: null,
  44. inApp: false,
  45. trust: null,
  46. errors: null,
  47. vars: null,
  48. },
  49. {
  50. filename: 'puma (3.12.6) lib/puma/server.rb',
  51. absPath: 'puma (3.12.6) lib/puma/server.rb',
  52. module: null,
  53. package: null,
  54. platform: null,
  55. instructionAddr: null,
  56. symbolAddr: null,
  57. function: 'block in run',
  58. rawFunction: null,
  59. symbol: null,
  60. context: [],
  61. lineNo: 334,
  62. colNo: null,
  63. inApp: false,
  64. trust: null,
  65. errors: null,
  66. vars: null,
  67. },
  68. {
  69. filename: 'sentry/controllers/welcome_controller.rb',
  70. absPath: 'sentry/controllers/welcome_controller.rb',
  71. module: null,
  72. package: null,
  73. platform: null,
  74. instructionAddr: null,
  75. symbolAddr: null,
  76. function: 'index',
  77. rawFunction: null,
  78. symbol: null,
  79. context: [
  80. [2, ' before_action :set_sentry_context\n'],
  81. [3, '\n'],
  82. [4, ' def index\n'],
  83. [5, ' 1 / 0\n'],
  84. [6, ' end\n'],
  85. [7, '\n'],
  86. [8, ' def view_error\n'],
  87. ],
  88. lineNo: 5,
  89. colNo: null,
  90. inApp: true,
  91. trust: null,
  92. errors: null,
  93. vars: null,
  94. minGroupingLevel: 1,
  95. },
  96. {
  97. filename: 'sentry/controllers/welcome_controller.rb',
  98. absPath: 'sentry/controllers/welcome_controller.rb',
  99. module: null,
  100. package: null,
  101. platform: null,
  102. instructionAddr: null,
  103. symbolAddr: null,
  104. function: '/',
  105. rawFunction: null,
  106. symbol: null,
  107. context: [
  108. [2, ' before_action :set_sentry_context\n'],
  109. [3, '\n'],
  110. [4, ' def index\n'],
  111. [5, ' 1 / 0\n'],
  112. [6, ' end\n'],
  113. [7, '\n'],
  114. [8, ' def view_error\n'],
  115. ],
  116. lineNo: 5,
  117. colNo: null,
  118. inApp: true,
  119. trust: null,
  120. errors: null,
  121. vars: null,
  122. minGroupingLevel: 0,
  123. },
  124. ],
  125. framesOmitted: null,
  126. registers: null,
  127. hasSystemFrames: true,
  128. },
  129. rawStacktrace: null,
  130. frames: null,
  131. },
  132. ],
  133. hasSystemFrames: true,
  134. excOmitted: null,
  135. },
  136. type: EntryType.EXCEPTION,
  137. },
  138. {
  139. data: {
  140. values: [
  141. {
  142. id: 13920,
  143. current: true,
  144. crashed: true,
  145. name: 'puma 002',
  146. stacktrace: null,
  147. rawStacktrace: null,
  148. },
  149. ],
  150. },
  151. type: EntryType.THREADS,
  152. },
  153. ],
  154. dist: null,
  155. message: '',
  156. title: 'ZeroDivisionError: divided by 0',
  157. location: 'sentry/controllers/welcome_controller.rb',
  158. user: null,
  159. contexts: {},
  160. sdk: null,
  161. context: {},
  162. packages: {},
  163. type: EventOrGroupType.ERROR,
  164. metadata: {
  165. display_title_with_tree_label: false,
  166. filename: 'sentry/controllers/welcome_controller.rb',
  167. finest_tree_label: [
  168. {filebase: 'welcome_controller.rb', function: '/'},
  169. {filebase: 'welcome_controller.rb', function: 'index'},
  170. ],
  171. function: '/',
  172. type: 'ZeroDivisionError',
  173. value: 'divided by 0',
  174. },
  175. tags: [{key: 'level', value: 'error'}],
  176. platform: 'other',
  177. dateReceived: '2021-10-28T12:28:22.318469Z',
  178. errors: [],
  179. crashFile: null,
  180. culprit: 'sentry/controllers/welcome_controller.rb in /',
  181. dateCreated: '2021-10-28T12:28:22.318469Z',
  182. fingerprints: ['58f1f47bea5239ea25397888dc9253d1'],
  183. groupingConfig: {
  184. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  185. id: 'mobile:2021-02-12',
  186. },
  187. release: null,
  188. userReport: null,
  189. sdkUpdates: [],
  190. nextEventID: null,
  191. previousEventID: null,
  192. occurrence: null,
  193. };
  194. const props: React.ComponentProps<typeof Threads> = {
  195. data: event.entries[1].data as React.ComponentProps<typeof Threads>['data'],
  196. event,
  197. groupingCurrentLevel: 0,
  198. hasHierarchicalGrouping: true,
  199. projectSlug: project.slug,
  200. organization,
  201. };
  202. it('renders', function () {
  203. render(<Threads {...props} />, {
  204. organization,
  205. });
  206. // Title
  207. expect(screen.getByRole('heading', {name: 'Stack Trace'})).toBeInTheDocument();
  208. // Actions
  209. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  210. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  211. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  212. // Stack Trace
  213. expect(
  214. screen.getByRole('heading', {name: 'ZeroDivisionError'})
  215. ).toBeInTheDocument();
  216. expect(screen.getByText('divided by 0')).toBeInTheDocument();
  217. expect(screen.getByTestId('stack-trace-content-v2')).toBeInTheDocument();
  218. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  219. });
  220. it('toggle full stack trace button', async function () {
  221. render(<Threads {...props} />, {organization});
  222. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  223. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  224. await userEvent.click(screen.getByRole('radio', {name: 'Full Stack Trace'}));
  225. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeChecked();
  226. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(4);
  227. });
  228. it('toggle sort by display option', async function () {
  229. render(<Threads {...props} />, {organization});
  230. expect(
  231. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  232. 'sentry/controllers/welcome_controller.rb'
  233. )
  234. ).toBeInTheDocument();
  235. // Sort by options
  236. expect(screen.getByText('Newest')).toBeInTheDocument();
  237. expect(screen.queryByText('Oldest')).not.toBeInTheDocument();
  238. // Switch to recent last
  239. await userEvent.click(screen.getByText('Newest'));
  240. await userEvent.click(screen.getByText('Oldest'));
  241. // Recent last is checked
  242. expect(screen.getByText('Oldest')).toBeInTheDocument();
  243. expect(screen.queryByText('Newest')).not.toBeInTheDocument();
  244. // Last frame is the first on the list
  245. expect(
  246. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  247. 'puma (3.12.6) lib/puma/server.rb'
  248. )
  249. ).toBeInTheDocument();
  250. // Click on recent first
  251. await userEvent.click(screen.getByText('Oldest'));
  252. await userEvent.click(screen.getByText('Newest'));
  253. // First frame is the first on the list
  254. expect(
  255. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  256. 'sentry/controllers/welcome_controller.rb'
  257. )
  258. ).toBeInTheDocument();
  259. });
  260. it('check display options', async function () {
  261. render(<Threads {...props} />, {organization});
  262. await userEvent.click(screen.getByRole('button', {name: 'Options'}));
  263. expect(await screen.findByText('Display')).toBeInTheDocument();
  264. Object.entries(displayOptions).forEach(([key, value]) => {
  265. if (key === 'minified' || key === 'raw-stack-trace') {
  266. expect(screen.getByText(value)).toBeInTheDocument();
  267. return;
  268. }
  269. expect(screen.queryByText(value)).not.toBeInTheDocument();
  270. });
  271. // Hover over the Minified option
  272. await userEvent.hover(screen.getByText(displayOptions.minified));
  273. // Minified option is disabled
  274. expect(
  275. await screen.findByText('Minified version not available')
  276. ).toBeInTheDocument();
  277. });
  278. });
  279. });
  280. describe('native platform', function () {
  281. describe('cocoa', function () {
  282. const event: Event = {
  283. id: 'bfe4379d82934b2b91d70b1167bcae8d',
  284. groupID: '24',
  285. eventID: 'bfe4379d82934b2b91d70b1167bcae8d',
  286. projectID: '2',
  287. size: 89101,
  288. entries: [
  289. {
  290. data: {
  291. values: [
  292. {
  293. stacktrace: {
  294. frames: [
  295. {
  296. filename: null,
  297. absPath: null,
  298. module: null,
  299. package:
  300. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  301. platform: null,
  302. instructionAddr: '0x1000adb08',
  303. symbolAddr: '0x1000ad5c4',
  304. function:
  305. '__44-[SentryBreadcrumbTracker swizzleSendAction]_block_invoke_2',
  306. rawFunction: null,
  307. symbol: null,
  308. context: [],
  309. lineNo: null,
  310. colNo: null,
  311. inApp: false,
  312. trust: null,
  313. errors: null,
  314. vars: null,
  315. },
  316. {
  317. filename: null,
  318. absPath: null,
  319. module: null,
  320. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  321. platform: null,
  322. instructionAddr: '0x197885c54',
  323. symbolAddr: '0x197885bf4',
  324. function: '<redacted>',
  325. rawFunction: null,
  326. symbol: null,
  327. context: [],
  328. lineNo: null,
  329. colNo: null,
  330. inApp: false,
  331. trust: null,
  332. errors: null,
  333. vars: null,
  334. },
  335. {
  336. filename: null,
  337. absPath: null,
  338. module: null,
  339. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  340. platform: null,
  341. instructionAddr: '0x197885c54',
  342. symbolAddr: '0x197885bf4',
  343. function: '<redacted>',
  344. rawFunction: null,
  345. symbol: null,
  346. context: [],
  347. lineNo: null,
  348. colNo: null,
  349. inApp: false,
  350. trust: null,
  351. errors: null,
  352. vars: null,
  353. },
  354. {
  355. filename: null,
  356. absPath: null,
  357. module: null,
  358. package:
  359. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  360. platform: null,
  361. instructionAddr: '0x10008c5ac',
  362. symbolAddr: '0x10008c500',
  363. function: 'ViewController.causeCrash',
  364. rawFunction: 'ViewController.causeCrash(Any) -> ()',
  365. symbol: null,
  366. context: [],
  367. lineNo: null,
  368. colNo: null,
  369. inApp: true,
  370. trust: null,
  371. errors: null,
  372. vars: null,
  373. },
  374. {
  375. filename: null,
  376. absPath: null,
  377. module: null,
  378. package:
  379. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  380. platform: null,
  381. instructionAddr: '0x1000b0bfc',
  382. symbolAddr: '0x1000b0be4',
  383. function: '-[SentryClient crash]',
  384. rawFunction: null,
  385. symbol: null,
  386. context: [],
  387. lineNo: null,
  388. colNo: null,
  389. inApp: false,
  390. trust: null,
  391. errors: null,
  392. vars: null,
  393. },
  394. ],
  395. framesOmitted: null,
  396. hasSystemFrames: true,
  397. registers: {
  398. cpsr: '0x60000000',
  399. fp: '0x16fd79870',
  400. lr: '0x10008c5ac',
  401. pc: '0x1000b0bfc',
  402. sp: '0x16fd79810',
  403. x0: '0x1700eee80',
  404. x1: '0x10008e49c',
  405. x10: '0x1b7886ff0',
  406. x11: '0x59160100591680',
  407. x12: '0x0',
  408. x13: '0x591600',
  409. x14: '0x591700',
  410. x15: '0x5916c0',
  411. x16: '0x591601',
  412. x17: '0x1000b0be4',
  413. x18: '0x0',
  414. x19: '0x1740eb200',
  415. x2: '0x0',
  416. x20: '0x10fd08db0',
  417. x21: '0x10008e4de',
  418. x22: '0x10fe0a470',
  419. x23: '0x10fe0a470',
  420. x24: '0x174008ba0',
  421. x25: '0x0',
  422. x26: '0x19838eb61',
  423. x27: '0x1',
  424. x28: '0x170046c60',
  425. x29: '0x16fd79870',
  426. x3: '0x1740eb200',
  427. x4: '0x1740eb200',
  428. x5: '0x1740eb200',
  429. x6: '0x0',
  430. x7: '0x2',
  431. x8: '0x0',
  432. x9: '0x1b7886fec',
  433. },
  434. },
  435. threadId: 0,
  436. module: null,
  437. mechanism: null,
  438. rawStacktrace: null,
  439. value:
  440. 'Attempted to dereference null pointer.\nOriginated at or in a subcall of ViewController.causeCrash(Any) -> ()',
  441. type: 'EXC_BAD_ACCESS',
  442. },
  443. ],
  444. hasSystemFrames: true,
  445. excOmitted: null,
  446. },
  447. type: EntryType.EXCEPTION,
  448. },
  449. {
  450. data: {
  451. values: [
  452. {
  453. id: 0,
  454. current: false,
  455. crashed: true,
  456. name: 'main',
  457. stacktrace: {
  458. frames: [
  459. {
  460. filename: null,
  461. absPath: null,
  462. module: null,
  463. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  464. platform: null,
  465. instructionAddr: '0x197885c54',
  466. symbolAddr: '0x197885bf4',
  467. function: '<redacted>',
  468. rawFunction: null,
  469. symbol: null,
  470. context: [],
  471. lineNo: null,
  472. colNo: null,
  473. inApp: false,
  474. trust: null,
  475. errors: null,
  476. vars: null,
  477. },
  478. {
  479. filename: null,
  480. absPath: null,
  481. module: null,
  482. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  483. platform: null,
  484. instructionAddr: '0x197885c54',
  485. symbolAddr: '0x197885bf4',
  486. function: '<redacted>',
  487. rawFunction: null,
  488. symbol: null,
  489. context: [],
  490. lineNo: null,
  491. colNo: null,
  492. inApp: false,
  493. trust: null,
  494. errors: null,
  495. vars: null,
  496. },
  497. {
  498. filename: null,
  499. absPath: null,
  500. module: null,
  501. package:
  502. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  503. platform: null,
  504. instructionAddr: '0x10008c630',
  505. symbolAddr: '0x10008c5e8',
  506. function: 'ViewController.causeCrash',
  507. rawFunction: '@objc ViewController.causeCrash(Any) -> ()',
  508. symbol: null,
  509. context: [],
  510. lineNo: null,
  511. colNo: null,
  512. inApp: true,
  513. trust: null,
  514. errors: null,
  515. vars: null,
  516. },
  517. {
  518. filename: null,
  519. absPath: null,
  520. module: null,
  521. package:
  522. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  523. platform: null,
  524. instructionAddr: '0x10008c5ac',
  525. symbolAddr: '0x10008c500',
  526. function: 'ViewController.causeCrash',
  527. rawFunction: 'ViewController.causeCrash(Any) -> ()',
  528. symbol: null,
  529. context: [],
  530. lineNo: null,
  531. colNo: null,
  532. inApp: true,
  533. trust: null,
  534. errors: null,
  535. vars: null,
  536. },
  537. {
  538. filename: null,
  539. absPath: null,
  540. module: null,
  541. package:
  542. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  543. platform: null,
  544. instructionAddr: '0x1000b0bfc',
  545. symbolAddr: '0x1000b0be4',
  546. function: '-[SentryClient crash]',
  547. rawFunction: null,
  548. symbol: null,
  549. context: [],
  550. lineNo: null,
  551. colNo: null,
  552. inApp: false,
  553. trust: null,
  554. errors: null,
  555. vars: null,
  556. },
  557. ],
  558. framesOmitted: null,
  559. registers: {
  560. cpsr: '0x60000000',
  561. fp: '0x16fd79870',
  562. lr: '0x10008c5ac',
  563. pc: '0x1000b0bfc',
  564. sp: '0x16fd79810',
  565. x0: '0x1700eee80',
  566. x1: '0x10008e49c',
  567. x10: '0x1b7886ff0',
  568. x11: '0x59160100591680',
  569. x12: '0x0',
  570. x13: '0x591600',
  571. x14: '0x591700',
  572. x15: '0x5916c0',
  573. x16: '0x591601',
  574. x17: '0x1000b0be4',
  575. x18: '0x0',
  576. x19: '0x1740eb200',
  577. x2: '0x0',
  578. x20: '0x10fd08db0',
  579. x21: '0x10008e4de',
  580. x22: '0x10fe0a470',
  581. x23: '0x10fe0a470',
  582. x24: '0x174008ba0',
  583. x25: '0x0',
  584. x26: '0x19838eb61',
  585. x27: '0x1',
  586. x28: '0x170046c60',
  587. x29: '0x16fd79870',
  588. x3: '0x1740eb200',
  589. x4: '0x1740eb200',
  590. x5: '0x1740eb200',
  591. x6: '0x0',
  592. x7: '0x2',
  593. x8: '0x0',
  594. x9: '0x1b7886fec',
  595. },
  596. hasSystemFrames: true,
  597. },
  598. rawStacktrace: null,
  599. state: 'BLOCKED',
  600. heldLocks: {
  601. '0x0d3a2f0a': {
  602. type: 8,
  603. address: '0x0d3a2f0a',
  604. package_name: 'java.lang',
  605. class_name: 'Object',
  606. thread_id: 11,
  607. },
  608. },
  609. },
  610. {
  611. id: 1,
  612. current: false,
  613. crashed: false,
  614. name: null,
  615. state: 'TIMED_WAITING',
  616. stacktrace: {
  617. frames: [
  618. {
  619. filename: null,
  620. absPath: null,
  621. module: null,
  622. package: '/usr/lib/system/libsystem_pthread.dylib',
  623. platform: null,
  624. instructionAddr: '0x1907df1a4',
  625. symbolAddr: '0x1907decb8',
  626. function: '_pthread_wqthread',
  627. rawFunction: null,
  628. symbol: null,
  629. context: [],
  630. lineNo: null,
  631. colNo: null,
  632. inApp: false,
  633. trust: null,
  634. errors: null,
  635. vars: null,
  636. },
  637. {
  638. filename: null,
  639. absPath: null,
  640. module: null,
  641. package: '/usr/lib/system/libsystem_kernel.dylib',
  642. platform: null,
  643. instructionAddr: '0x190719a88',
  644. symbolAddr: '0x190719a80',
  645. function: '__workq_kernreturn',
  646. rawFunction: null,
  647. symbol: null,
  648. context: [],
  649. lineNo: null,
  650. colNo: null,
  651. inApp: false,
  652. trust: null,
  653. errors: null,
  654. vars: null,
  655. },
  656. ],
  657. framesOmitted: null,
  658. registers: {
  659. cpsr: '0x0',
  660. fp: '0x16dfcaf70',
  661. lr: '0x1907df1a4',
  662. pc: '0x190719a88',
  663. sp: '0x16dfcaef0',
  664. x0: '0x4',
  665. x1: '0x0',
  666. x10: '0x1',
  667. x11: '0x0',
  668. x12: '0x30000400000d03',
  669. x13: '0x0',
  670. x14: '0x1740e9edc',
  671. x15: '0xfffffff100000000',
  672. x16: '0x170',
  673. x17: '0x191619c10',
  674. x18: '0x0',
  675. x19: '0x16dfcb000',
  676. x2: '0x0',
  677. x20: '0x19',
  678. x21: '0x270019',
  679. x22: '0x0',
  680. x23: '0xd03',
  681. x24: '0x1b788d000',
  682. x25: '0x1b788d000',
  683. x26: '0x0',
  684. x27: '0x80000000',
  685. x28: '0x800010ff',
  686. x29: '0x16dfcaf70',
  687. x3: '0x0',
  688. x4: '0x80010ff',
  689. x5: '0x0',
  690. x6: '0x0',
  691. x7: '0x0',
  692. x8: '0x2',
  693. x9: '0x17409b4ec',
  694. },
  695. hasSystemFrames: false,
  696. },
  697. rawStacktrace: null,
  698. },
  699. ],
  700. },
  701. type: EntryType.THREADS,
  702. },
  703. {
  704. data: {
  705. images: [
  706. {
  707. code_file:
  708. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  709. debug_id: '6b77ffb6-5aba-3b5f-9171-434f9660f738',
  710. image_addr: '0x100084000',
  711. image_size: 49152,
  712. image_vmaddr: '0x100000000',
  713. type: 'macho',
  714. candidates: [],
  715. features: {
  716. has_debug_info: false,
  717. has_sources: false,
  718. has_symbols: false,
  719. has_unwind_info: false,
  720. },
  721. },
  722. {
  723. code_file: '/System/Library/Frameworks/UIKit.framework/UIKit',
  724. debug_id: '314063bd-f85f-321d-88d6-e24a0de464a2',
  725. image_addr: '0x197841000',
  726. image_size: 14315520,
  727. image_vmaddr: '0x187769000',
  728. type: 'macho',
  729. candidates: [],
  730. features: {
  731. has_debug_info: false,
  732. has_sources: false,
  733. has_symbols: false,
  734. has_unwind_info: false,
  735. },
  736. },
  737. {
  738. code_file:
  739. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  740. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  741. image_addr: '0x1000a4000',
  742. image_size: 163840,
  743. image_vmaddr: '0x0',
  744. type: 'macho',
  745. candidates: [],
  746. features: {
  747. has_debug_info: false,
  748. has_sources: false,
  749. has_symbols: false,
  750. has_unwind_info: false,
  751. },
  752. },
  753. {
  754. code_file:
  755. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  756. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  757. image_addr: '0x1000a4000',
  758. image_size: 163840,
  759. image_vmaddr: '0x0',
  760. type: 'macho',
  761. candidates: [],
  762. features: {
  763. has_debug_info: false,
  764. has_sources: false,
  765. has_symbols: false,
  766. has_unwind_info: false,
  767. },
  768. },
  769. ],
  770. },
  771. type: EntryType.DEBUGMETA,
  772. },
  773. ],
  774. dist: '1',
  775. message: '',
  776. title: 'ViewController.causeCrash | main',
  777. location: null,
  778. user: {
  779. id: '1234',
  780. email: 'hello@sentry.io',
  781. username: null,
  782. ip_address: '172.18.0.1',
  783. name: null,
  784. data: null,
  785. },
  786. type: EventOrGroupType.ERROR,
  787. metadata: {
  788. display_title_with_tree_label: true,
  789. finest_tree_label: [
  790. {
  791. function: 'ViewController.causeCrash',
  792. },
  793. {
  794. function: 'main',
  795. },
  796. ],
  797. function: 'ViewController.causeCrash',
  798. value:
  799. 'Attempted to dereference null pointer.\nOriginated at or in a subcall of ViewController.causeCrash(Any) -> ()',
  800. },
  801. platform: 'cocoa',
  802. dateReceived: '2021-11-02T07:33:38.831104Z',
  803. errors: [
  804. {
  805. type: 'invalid_data',
  806. message: 'Discarded invalid value',
  807. data: {
  808. name: 'threads.values.9.stacktrace.frames',
  809. reason: 'expected a non-empty value',
  810. },
  811. },
  812. ],
  813. crashFile: null,
  814. culprit: '',
  815. dateCreated: '2021-11-02T07:33:38.831104Z',
  816. fingerprints: ['852f6cf1ed76d284b95e7d62275088ca'],
  817. groupingConfig: {
  818. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  819. id: 'mobile:2021-02-12',
  820. },
  821. tags: [],
  822. contexts: {},
  823. userReport: null,
  824. sdkUpdates: [],
  825. nextEventID: null,
  826. previousEventID: null,
  827. occurrence: null,
  828. };
  829. const props: React.ComponentProps<typeof Threads> = {
  830. data: event.entries[1].data as React.ComponentProps<typeof Threads>['data'],
  831. event,
  832. groupingCurrentLevel: 0,
  833. hasHierarchicalGrouping: true,
  834. projectSlug: project.slug,
  835. organization,
  836. };
  837. it('renders', function () {
  838. render(<Threads {...props} />, {organization});
  839. // Title
  840. const threadSelector = screen.getByTestId('thread-selector');
  841. expect(threadSelector).toBeInTheDocument();
  842. within(threadSelector).getByText('main');
  843. // Actions
  844. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  845. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  846. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  847. expect(screen.queryByText('Threads')).not.toBeInTheDocument();
  848. expect(screen.queryByText('Thread State')).not.toBeInTheDocument();
  849. expect(screen.queryByText('Thread Tags')).not.toBeInTheDocument();
  850. // Stack Trace
  851. expect(screen.getByRole('heading', {name: 'EXC_BAD_ACCESS'})).toBeInTheDocument();
  852. expect(
  853. screen.getByText(
  854. 'Attempted to dereference null pointer. Originated at or in a subcall of ViewController.causeCrash(Any) -> ()'
  855. )
  856. ).toBeInTheDocument();
  857. expect(screen.getByTestId('stack-trace')).toBeInTheDocument();
  858. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  859. });
  860. it('renders thread state and lock reason', function () {
  861. const newOrg = {
  862. ...organization,
  863. features: ['anr-improvements'],
  864. };
  865. const newProps = {...props, organization: newOrg};
  866. render(<Threads {...newProps} />, {organization: newOrg});
  867. // Title
  868. expect(screen.getByTestId('thread-selector')).toBeInTheDocument();
  869. expect(screen.getByText('Threads')).toBeInTheDocument();
  870. expect(screen.getByText('Thread State')).toBeInTheDocument();
  871. expect(screen.getAllByText('Blocked')).toHaveLength(2);
  872. expect(
  873. screen.getAllByText('waiting to lock <0x0d3a2f0a> held by thread 11')
  874. ).toHaveLength(2);
  875. expect(screen.getByText('Thread Tags')).toBeInTheDocument();
  876. // Actions
  877. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  878. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  879. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  880. // Stack Trace
  881. expect(screen.getByRole('heading', {name: 'EXC_BAD_ACCESS'})).toBeInTheDocument();
  882. expect(
  883. screen.getByText(
  884. 'Attempted to dereference null pointer. Originated at or in a subcall of ViewController.causeCrash(Any) -> ()'
  885. )
  886. ).toBeInTheDocument();
  887. expect(screen.getByTestId('stack-trace')).toBeInTheDocument();
  888. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  889. });
  890. it('hides thread tag event entry if none', function () {
  891. const newOrg = {
  892. ...organization,
  893. features: ['anr-improvements'],
  894. };
  895. const newProps = {
  896. ...props,
  897. data: {
  898. values: [
  899. {
  900. id: 0,
  901. current: false,
  902. crashed: true,
  903. name: null,
  904. stacktrace: {
  905. frames: [
  906. {
  907. filename: null,
  908. absPath: null,
  909. module: null,
  910. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  911. platform: null,
  912. instructionAddr: '0x197885c54',
  913. symbolAddr: '0x197885bf4',
  914. function: '<redacted>',
  915. rawFunction: null,
  916. symbol: null,
  917. context: [],
  918. lineNo: null,
  919. colNo: null,
  920. inApp: false,
  921. trust: null,
  922. errors: null,
  923. vars: null,
  924. },
  925. ],
  926. framesOmitted: null,
  927. registers: {
  928. cpsr: '0x60000000',
  929. fp: '0x16fd79870',
  930. lr: '0x10008c5ac',
  931. },
  932. hasSystemFrames: true,
  933. },
  934. rawStacktrace: null,
  935. },
  936. ],
  937. },
  938. organization: newOrg,
  939. };
  940. render(<Threads {...newProps} />, {organization: newOrg});
  941. expect(screen.queryByText('Thread Tags')).not.toBeInTheDocument();
  942. });
  943. it('maps android vm states to java vm states', function () {
  944. const newEvent = {...event};
  945. const threadsEntry = newEvent.entries[1].data as React.ComponentProps<
  946. typeof Threads
  947. >['data'];
  948. const thread = {
  949. id: 0,
  950. current: false,
  951. crashed: true,
  952. name: 'main',
  953. stacktrace: {
  954. frames: [
  955. {
  956. filename: null,
  957. absPath: null,
  958. module: null,
  959. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  960. platform: null,
  961. instructionAddr: '0x197885c54',
  962. symbolAddr: '0x197885bf4',
  963. function: '<redacted>',
  964. rawFunction: null,
  965. symbol: null,
  966. context: [],
  967. lineNo: null,
  968. colNo: null,
  969. inApp: false,
  970. trust: null,
  971. errors: null,
  972. vars: null,
  973. },
  974. ],
  975. registers: {},
  976. framesOmitted: null,
  977. hasSystemFrames: true,
  978. },
  979. rawStacktrace: null,
  980. state: 'WaitingPerformingGc',
  981. };
  982. threadsEntry.values = [
  983. {
  984. ...thread,
  985. },
  986. {
  987. ...thread,
  988. id: 1,
  989. },
  990. ];
  991. const newOrg = {
  992. ...organization,
  993. features: ['anr-improvements'],
  994. };
  995. const newProps = {...props, event: newEvent, organization: newOrg};
  996. render(<Threads {...newProps} />, {organization: newOrg});
  997. // Title
  998. expect(screen.getByTestId('thread-selector')).toBeInTheDocument();
  999. expect(screen.getByText('Threads')).toBeInTheDocument();
  1000. expect(screen.getByText('Thread State')).toBeInTheDocument();
  1001. // WaitingPerformingGc maps to Waiting for both Thread tag and Thread State
  1002. expect(screen.getByText('Thread Tags')).toBeInTheDocument();
  1003. expect(screen.getAllByText('Waiting')).toHaveLength(2);
  1004. });
  1005. it('toggle full stack trace button', async function () {
  1006. render(<Threads {...props} />, {organization});
  1007. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  1008. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  1009. await userEvent.click(screen.getByRole('radio', {name: 'Full Stack Trace'}));
  1010. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeChecked();
  1011. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(4);
  1012. });
  1013. it('toggle sort by option', async function () {
  1014. render(<Threads {...props} />, {organization});
  1015. expect(
  1016. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  1017. '-[SentryClient crash]'
  1018. )
  1019. ).toBeInTheDocument();
  1020. await userEvent.click(screen.getByRole('button', {name: 'Newest'}));
  1021. // Sort by options
  1022. expect(screen.getAllByText('Newest')).toHaveLength(2);
  1023. expect(screen.getByText('Oldest')).toBeInTheDocument();
  1024. // Recent first is checked by default
  1025. expect(screen.getByRole('option', {name: 'Newest'})).toHaveAttribute(
  1026. 'aria-selected',
  1027. 'true'
  1028. );
  1029. // Click on recent last
  1030. await userEvent.click(screen.getByText('Oldest'));
  1031. // Recent last is enabled
  1032. expect(screen.queryByText('Newest')).not.toBeInTheDocument();
  1033. expect(screen.getByText('Oldest')).toBeInTheDocument();
  1034. // Last frame is the first on the list
  1035. expect(
  1036. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText('UIKit')
  1037. ).toBeInTheDocument();
  1038. // Switch back to recent first
  1039. await userEvent.click(screen.getByRole('button', {name: 'Oldest'}));
  1040. await userEvent.click(screen.getByText('Newest'));
  1041. // First frame is the first on the list
  1042. expect(
  1043. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  1044. '-[SentryClient crash]'
  1045. )
  1046. ).toBeInTheDocument();
  1047. });
  1048. it('check display options', async function () {
  1049. render(<Threads {...props} />, {organization});
  1050. await userEvent.click(screen.getByRole('button', {name: 'Options'}));
  1051. expect(await screen.findByText('Display')).toBeInTheDocument();
  1052. Object.values(displayOptions).forEach(value => {
  1053. expect(screen.getByText(value)).toBeInTheDocument();
  1054. });
  1055. // Hover over absolute file paths option
  1056. await userEvent.hover(screen.getByText(displayOptions['absolute-file-paths']));
  1057. // Absolute file paths option is disabled
  1058. expect(
  1059. await screen.findByText('Absolute file paths not available')
  1060. ).toBeInTheDocument();
  1061. // Hover over Minified option
  1062. await userEvent.hover(screen.getByText(displayOptions.minified));
  1063. // Minified option is disabled
  1064. expect(
  1065. await screen.findByText('Unsymbolicated version not available')
  1066. ).toBeInTheDocument();
  1067. // Function name is not verbose
  1068. expect(
  1069. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  1070. 'ViewController.causeCrash'
  1071. )
  1072. ).toBeInTheDocument();
  1073. // Click on verbose function name option
  1074. await userEvent.click(screen.getByText(displayOptions['verbose-function-names']));
  1075. // Function name is now verbose
  1076. expect(
  1077. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  1078. 'ViewController.causeCrash(Any) -> ()'
  1079. )
  1080. ).toBeInTheDocument();
  1081. // Address is not absolute
  1082. expect(
  1083. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('+0x085ac')
  1084. ).toBeInTheDocument();
  1085. // Click on absolute file paths option
  1086. await userEvent.click(screen.getByText(displayOptions['absolute-addresses']));
  1087. // Address is now absolute
  1088. expect(
  1089. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('0x10008c5ac')
  1090. ).toBeInTheDocument();
  1091. MockApiClient.addMockResponse({
  1092. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=false`,
  1093. body: '',
  1094. });
  1095. // Click on raw stack trace option
  1096. await userEvent.click(screen.getByText(displayOptions['raw-stack-trace']));
  1097. // Download button is displayed
  1098. expect(screen.getByRole('button', {name: 'Download'})).toBeInTheDocument();
  1099. // Full stack trace toggler is not displayed
  1100. expect(
  1101. screen.queryByRole('radio', {name: 'Full Stack Trace'})
  1102. ).not.toBeInTheDocument();
  1103. // Raw content is displayed
  1104. expect(screen.queryByRole('list', {name: 'Stack trace'})).not.toBeInTheDocument();
  1105. // Raw content and the Raw stack trace option
  1106. expect(screen.getAllByTestId('raw-stack-trace')).toHaveLength(2);
  1107. // Raw stack trace option
  1108. expect(screen.getByRole('option', {name: 'Raw stack trace'})).toBeInTheDocument();
  1109. });
  1110. it('uses thread label in selector if name not available', function () {
  1111. const newEvent = {...event};
  1112. const threadsEntry = newEvent.entries[1].data as React.ComponentProps<
  1113. typeof Threads
  1114. >['data'];
  1115. const thread = {
  1116. id: 0,
  1117. current: false,
  1118. crashed: true,
  1119. name: null,
  1120. stacktrace: {
  1121. frames: [
  1122. {
  1123. filename: null,
  1124. absPath: null,
  1125. module: null,
  1126. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  1127. platform: null,
  1128. instructionAddr: '0x197885c54',
  1129. symbolAddr: '0x197885bf4',
  1130. function: '<redacted>',
  1131. rawFunction: null,
  1132. symbol: null,
  1133. context: [],
  1134. lineNo: null,
  1135. colNo: null,
  1136. inApp: false,
  1137. trust: null,
  1138. errors: null,
  1139. vars: null,
  1140. },
  1141. ],
  1142. registers: {},
  1143. framesOmitted: null,
  1144. hasSystemFrames: true,
  1145. },
  1146. rawStacktrace: null,
  1147. };
  1148. threadsEntry.values = [
  1149. {
  1150. ...thread,
  1151. },
  1152. {
  1153. ...thread,
  1154. id: 1,
  1155. },
  1156. ];
  1157. const newProps = {...props, event: newEvent};
  1158. render(<Threads {...newProps} />, {organization});
  1159. // Title
  1160. const threadSelector = screen.getByTestId('thread-selector');
  1161. expect(threadSelector).toBeInTheDocument();
  1162. within(threadSelector).getByText('ViewController.causeCrash');
  1163. });
  1164. it('renders raw stack trace', async function () {
  1165. MockApiClient.addMockResponse({
  1166. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=false`,
  1167. body: 'crash report content',
  1168. });
  1169. MockApiClient.addMockResponse({
  1170. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=true`,
  1171. body: 'crash report content (minified)',
  1172. });
  1173. // Need rawStacktrace: true to enable the "minified" option in the UI
  1174. const eventWithMinifiedOption = merge({}, event, {
  1175. entries: [{data: {values: [{rawStacktrace: true}]}}],
  1176. });
  1177. render(<Threads {...props} event={eventWithMinifiedOption} />, {organization});
  1178. await userEvent.click(screen.getByRole('button', {name: 'Options'}));
  1179. expect(await screen.findByText('Display')).toBeInTheDocument();
  1180. // Click on raw stack trace option
  1181. await userEvent.click(screen.getByText(displayOptions['raw-stack-trace']));
  1182. // Raw crash report content should be displayed
  1183. await screen.findByText('crash report content');
  1184. // Download button should have correct URL
  1185. expect(screen.getByRole('button', {name: 'Download'})).toHaveAttribute(
  1186. 'href',
  1187. `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=false&download=1`
  1188. );
  1189. // Click on minified option
  1190. await userEvent.click(screen.getByText(displayOptions.minified));
  1191. // Raw crash report content should be displayed (now with minified response)
  1192. await screen.findByText('crash report content (minified)');
  1193. // Download button should nonw have minified=true
  1194. expect(screen.getByRole('button', {name: 'Download'})).toHaveAttribute(
  1195. 'href',
  1196. `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=true&download=1`
  1197. );
  1198. });
  1199. });
  1200. });
  1201. });