threads.spec.tsx 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  3. import {Threads} from 'sentry/components/events/interfaces/threads';
  4. import {displayOptions} from 'sentry/components/events/traceEventDataSection';
  5. import {EventOrGroupType} from 'sentry/types';
  6. import {EntryType, Event} from 'sentry/types/event';
  7. describe('Threads', function () {
  8. const {project, organization} = initializeOrg();
  9. describe('non native platform', function () {
  10. describe('other platform', function () {
  11. const event: Event = {
  12. id: '020eb33f6ce64ed6adc60f8993535816',
  13. groupID: '68',
  14. eventID: '020eb33f6ce64ed6adc60f8993535816',
  15. projectID: '2',
  16. size: 3481,
  17. entries: [
  18. {
  19. data: {
  20. values: [
  21. {
  22. type: 'ZeroDivisionError',
  23. value: 'divided by 0',
  24. mechanism: null,
  25. threadId: null,
  26. module: '',
  27. stacktrace: {
  28. frames: [
  29. {
  30. filename: 'puma (3.12.6) lib/puma/thread_pool.rb',
  31. absPath: 'puma (3.12.6) lib/puma/thread_pool.rb',
  32. module: null,
  33. package: null,
  34. platform: null,
  35. instructionAddr: null,
  36. symbolAddr: null,
  37. function: 'block in spawn_thread',
  38. rawFunction: null,
  39. symbol: null,
  40. context: [],
  41. lineNo: 135,
  42. colNo: null,
  43. inApp: false,
  44. trust: null,
  45. errors: null,
  46. vars: null,
  47. },
  48. {
  49. filename: 'puma (3.12.6) lib/puma/server.rb',
  50. absPath: 'puma (3.12.6) lib/puma/server.rb',
  51. module: null,
  52. package: null,
  53. platform: null,
  54. instructionAddr: null,
  55. symbolAddr: null,
  56. function: 'block in run',
  57. rawFunction: null,
  58. symbol: null,
  59. context: [],
  60. lineNo: 334,
  61. colNo: null,
  62. inApp: false,
  63. trust: null,
  64. errors: null,
  65. vars: null,
  66. },
  67. {
  68. filename: 'sentry/controllers/welcome_controller.rb',
  69. absPath: 'sentry/controllers/welcome_controller.rb',
  70. module: null,
  71. package: null,
  72. platform: null,
  73. instructionAddr: null,
  74. symbolAddr: null,
  75. function: 'index',
  76. rawFunction: null,
  77. symbol: null,
  78. context: [
  79. [2, ' before_action :set_sentry_context\n'],
  80. [3, '\n'],
  81. [4, ' def index\n'],
  82. [5, ' 1 / 0\n'],
  83. [6, ' end\n'],
  84. [7, '\n'],
  85. [8, ' def view_error\n'],
  86. ],
  87. lineNo: 5,
  88. colNo: null,
  89. inApp: true,
  90. trust: null,
  91. errors: null,
  92. vars: null,
  93. minGroupingLevel: 1,
  94. },
  95. {
  96. filename: 'sentry/controllers/welcome_controller.rb',
  97. absPath: 'sentry/controllers/welcome_controller.rb',
  98. module: null,
  99. package: null,
  100. platform: null,
  101. instructionAddr: null,
  102. symbolAddr: null,
  103. function: '/',
  104. rawFunction: null,
  105. symbol: null,
  106. context: [
  107. [2, ' before_action :set_sentry_context\n'],
  108. [3, '\n'],
  109. [4, ' def index\n'],
  110. [5, ' 1 / 0\n'],
  111. [6, ' end\n'],
  112. [7, '\n'],
  113. [8, ' def view_error\n'],
  114. ],
  115. lineNo: 5,
  116. colNo: null,
  117. inApp: true,
  118. trust: null,
  119. errors: null,
  120. vars: null,
  121. minGroupingLevel: 0,
  122. },
  123. ],
  124. framesOmitted: null,
  125. registers: null,
  126. hasSystemFrames: true,
  127. },
  128. rawStacktrace: null,
  129. frames: null,
  130. },
  131. ],
  132. hasSystemFrames: true,
  133. excOmitted: null,
  134. },
  135. type: EntryType.EXCEPTION,
  136. },
  137. {
  138. data: {
  139. values: [
  140. {
  141. id: 13920,
  142. current: true,
  143. crashed: true,
  144. name: 'puma 002',
  145. stacktrace: null,
  146. rawStacktrace: null,
  147. },
  148. ],
  149. },
  150. type: EntryType.THREADS,
  151. },
  152. ],
  153. dist: null,
  154. message: '',
  155. title: 'ZeroDivisionError: divided by 0',
  156. location: 'sentry/controllers/welcome_controller.rb',
  157. user: null,
  158. contexts: {},
  159. sdk: null,
  160. context: {},
  161. packages: {},
  162. type: EventOrGroupType.ERROR,
  163. metadata: {
  164. display_title_with_tree_label: false,
  165. filename: 'sentry/controllers/welcome_controller.rb',
  166. finest_tree_label: [
  167. {filebase: 'welcome_controller.rb', function: '/'},
  168. {filebase: 'welcome_controller.rb', function: 'index'},
  169. ],
  170. function: '/',
  171. type: 'ZeroDivisionError',
  172. value: 'divided by 0',
  173. },
  174. tags: [{key: 'level', value: 'error'}],
  175. platform: 'other',
  176. dateReceived: '2021-10-28T12:28:22.318469Z',
  177. errors: [],
  178. crashFile: null,
  179. culprit: 'sentry/controllers/welcome_controller.rb in /',
  180. dateCreated: '2021-10-28T12:28:22.318469Z',
  181. fingerprints: ['58f1f47bea5239ea25397888dc9253d1'],
  182. groupingConfig: {
  183. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  184. id: 'mobile:2021-02-12',
  185. },
  186. release: null,
  187. userReport: null,
  188. sdkUpdates: [],
  189. nextEventID: null,
  190. previousEventID: null,
  191. occurrence: null,
  192. };
  193. const props: React.ComponentProps<typeof Threads> = {
  194. data: event.entries[1].data as React.ComponentProps<typeof Threads>['data'],
  195. event,
  196. groupingCurrentLevel: 0,
  197. hasHierarchicalGrouping: true,
  198. projectSlug: project.slug,
  199. organization,
  200. };
  201. it('renders', function () {
  202. const {container} = render(<Threads {...props} />, {
  203. organization,
  204. });
  205. // Title
  206. expect(screen.getByRole('heading', {name: 'Stack Trace'})).toBeInTheDocument();
  207. // Actions
  208. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  209. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  210. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  211. // Stack Trace
  212. expect(
  213. screen.getByRole('heading', {name: 'ZeroDivisionError'})
  214. ).toBeInTheDocument();
  215. expect(screen.getByText('divided by 0')).toBeInTheDocument();
  216. expect(screen.getByTestId('stack-trace-content-v2')).toBeInTheDocument();
  217. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  218. expect(container).toSnapshot();
  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. lockReason: 'waiting on tid=1',
  601. },
  602. {
  603. id: 1,
  604. current: false,
  605. crashed: false,
  606. name: null,
  607. state: 'TIMED_WAITING',
  608. stacktrace: {
  609. frames: [
  610. {
  611. filename: null,
  612. absPath: null,
  613. module: null,
  614. package: '/usr/lib/system/libsystem_pthread.dylib',
  615. platform: null,
  616. instructionAddr: '0x1907df1a4',
  617. symbolAddr: '0x1907decb8',
  618. function: '_pthread_wqthread',
  619. rawFunction: null,
  620. symbol: null,
  621. context: [],
  622. lineNo: null,
  623. colNo: null,
  624. inApp: false,
  625. trust: null,
  626. errors: null,
  627. vars: null,
  628. },
  629. {
  630. filename: null,
  631. absPath: null,
  632. module: null,
  633. package: '/usr/lib/system/libsystem_kernel.dylib',
  634. platform: null,
  635. instructionAddr: '0x190719a88',
  636. symbolAddr: '0x190719a80',
  637. function: '__workq_kernreturn',
  638. rawFunction: null,
  639. symbol: null,
  640. context: [],
  641. lineNo: null,
  642. colNo: null,
  643. inApp: false,
  644. trust: null,
  645. errors: null,
  646. vars: null,
  647. },
  648. ],
  649. framesOmitted: null,
  650. registers: {
  651. cpsr: '0x0',
  652. fp: '0x16dfcaf70',
  653. lr: '0x1907df1a4',
  654. pc: '0x190719a88',
  655. sp: '0x16dfcaef0',
  656. x0: '0x4',
  657. x1: '0x0',
  658. x10: '0x1',
  659. x11: '0x0',
  660. x12: '0x30000400000d03',
  661. x13: '0x0',
  662. x14: '0x1740e9edc',
  663. x15: '0xfffffff100000000',
  664. x16: '0x170',
  665. x17: '0x191619c10',
  666. x18: '0x0',
  667. x19: '0x16dfcb000',
  668. x2: '0x0',
  669. x20: '0x19',
  670. x21: '0x270019',
  671. x22: '0x0',
  672. x23: '0xd03',
  673. x24: '0x1b788d000',
  674. x25: '0x1b788d000',
  675. x26: '0x0',
  676. x27: '0x80000000',
  677. x28: '0x800010ff',
  678. x29: '0x16dfcaf70',
  679. x3: '0x0',
  680. x4: '0x80010ff',
  681. x5: '0x0',
  682. x6: '0x0',
  683. x7: '0x0',
  684. x8: '0x2',
  685. x9: '0x17409b4ec',
  686. },
  687. hasSystemFrames: false,
  688. },
  689. rawStacktrace: null,
  690. },
  691. ],
  692. },
  693. type: EntryType.THREADS,
  694. },
  695. {
  696. data: {
  697. images: [
  698. {
  699. code_file:
  700. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  701. debug_id: '6b77ffb6-5aba-3b5f-9171-434f9660f738',
  702. image_addr: '0x100084000',
  703. image_size: 49152,
  704. image_vmaddr: '0x100000000',
  705. type: 'macho',
  706. candidates: [],
  707. features: {
  708. has_debug_info: false,
  709. has_sources: false,
  710. has_symbols: false,
  711. has_unwind_info: false,
  712. },
  713. },
  714. {
  715. code_file: '/System/Library/Frameworks/UIKit.framework/UIKit',
  716. debug_id: '314063bd-f85f-321d-88d6-e24a0de464a2',
  717. image_addr: '0x197841000',
  718. image_size: 14315520,
  719. image_vmaddr: '0x187769000',
  720. type: 'macho',
  721. candidates: [],
  722. features: {
  723. has_debug_info: false,
  724. has_sources: false,
  725. has_symbols: false,
  726. has_unwind_info: false,
  727. },
  728. },
  729. {
  730. code_file:
  731. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  732. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  733. image_addr: '0x1000a4000',
  734. image_size: 163840,
  735. image_vmaddr: '0x0',
  736. type: 'macho',
  737. candidates: [],
  738. features: {
  739. has_debug_info: false,
  740. has_sources: false,
  741. has_symbols: false,
  742. has_unwind_info: false,
  743. },
  744. },
  745. {
  746. code_file:
  747. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  748. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  749. image_addr: '0x1000a4000',
  750. image_size: 163840,
  751. image_vmaddr: '0x0',
  752. type: 'macho',
  753. candidates: [],
  754. features: {
  755. has_debug_info: false,
  756. has_sources: false,
  757. has_symbols: false,
  758. has_unwind_info: false,
  759. },
  760. },
  761. ],
  762. },
  763. type: EntryType.DEBUGMETA,
  764. },
  765. ],
  766. dist: '1',
  767. message: '',
  768. title: 'ViewController.causeCrash | main',
  769. location: null,
  770. user: {
  771. id: '1234',
  772. email: 'hello@sentry.io',
  773. username: null,
  774. ip_address: '172.18.0.1',
  775. name: null,
  776. data: null,
  777. },
  778. type: EventOrGroupType.ERROR,
  779. metadata: {
  780. display_title_with_tree_label: true,
  781. finest_tree_label: [
  782. {
  783. function: 'ViewController.causeCrash',
  784. },
  785. {
  786. function: 'main',
  787. },
  788. ],
  789. function: 'ViewController.causeCrash',
  790. value:
  791. 'Attempted to dereference null pointer.\nOriginated at or in a subcall of ViewController.causeCrash(Any) -> ()',
  792. },
  793. platform: 'cocoa',
  794. dateReceived: '2021-11-02T07:33:38.831104Z',
  795. errors: [
  796. {
  797. type: 'invalid_data',
  798. message: 'Discarded invalid value',
  799. data: {
  800. name: 'threads.values.9.stacktrace.frames',
  801. reason: 'expected a non-empty value',
  802. },
  803. },
  804. ],
  805. crashFile: null,
  806. culprit: '',
  807. dateCreated: '2021-11-02T07:33:38.831104Z',
  808. fingerprints: ['852f6cf1ed76d284b95e7d62275088ca'],
  809. groupingConfig: {
  810. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  811. id: 'mobile:2021-02-12',
  812. },
  813. tags: [],
  814. contexts: {},
  815. userReport: null,
  816. sdkUpdates: [],
  817. nextEventID: null,
  818. previousEventID: null,
  819. occurrence: null,
  820. };
  821. const props: React.ComponentProps<typeof Threads> = {
  822. data: event.entries[1].data as React.ComponentProps<typeof Threads>['data'],
  823. event,
  824. groupingCurrentLevel: 0,
  825. hasHierarchicalGrouping: true,
  826. projectSlug: project.slug,
  827. organization,
  828. };
  829. it('renders', function () {
  830. const {container} = render(<Threads {...props} />, {organization});
  831. // Title
  832. const threadSelector = screen.getByTestId('thread-selector');
  833. expect(threadSelector).toBeInTheDocument();
  834. within(threadSelector).getByText('main');
  835. // Actions
  836. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  837. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  838. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  839. expect(screen.queryByText('Threads')).not.toBeInTheDocument();
  840. expect(screen.queryByText('Thread State')).not.toBeInTheDocument();
  841. expect(screen.queryByText('Thread Tags')).not.toBeInTheDocument();
  842. // Stack Trace
  843. expect(screen.getByRole('heading', {name: 'EXC_BAD_ACCESS'})).toBeInTheDocument();
  844. expect(
  845. screen.getByText(
  846. 'Attempted to dereference null pointer. Originated at or in a subcall of ViewController.causeCrash(Any) -> ()'
  847. )
  848. ).toBeInTheDocument();
  849. expect(screen.getByTestId('stack-trace')).toBeInTheDocument();
  850. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  851. expect(container).toSnapshot();
  852. });
  853. it('renders thread state and lock reason', function () {
  854. const newOrg = {
  855. ...organization,
  856. features: ['anr-improvements'],
  857. };
  858. const newProps = {...props, organization: newOrg};
  859. const {container} = render(<Threads {...newProps} />, {organization: newOrg});
  860. // Title
  861. expect(screen.getByTestId('thread-selector')).toBeInTheDocument();
  862. expect(screen.getByText('Threads')).toBeInTheDocument();
  863. expect(screen.getByText('Thread State')).toBeInTheDocument();
  864. expect(screen.getAllByText('Blocked')).toHaveLength(2);
  865. expect(screen.getAllByText('waiting on tid=1')).toHaveLength(2);
  866. expect(screen.getByText('Thread Tags')).toBeInTheDocument();
  867. // Actions
  868. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeInTheDocument();
  869. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  870. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  871. // Stack Trace
  872. expect(screen.getByRole('heading', {name: 'EXC_BAD_ACCESS'})).toBeInTheDocument();
  873. expect(
  874. screen.getByText(
  875. 'Attempted to dereference null pointer. Originated at or in a subcall of ViewController.causeCrash(Any) -> ()'
  876. )
  877. ).toBeInTheDocument();
  878. expect(screen.getByTestId('stack-trace')).toBeInTheDocument();
  879. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  880. expect(container).toSnapshot();
  881. });
  882. it('hides thread tag event entry if none', function () {
  883. const newOrg = {
  884. ...organization,
  885. features: ['anr-improvements'],
  886. };
  887. const newProps = {
  888. ...props,
  889. data: {
  890. values: [
  891. {
  892. id: 0,
  893. current: false,
  894. crashed: true,
  895. name: null,
  896. stacktrace: {
  897. frames: [
  898. {
  899. filename: null,
  900. absPath: null,
  901. module: null,
  902. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  903. platform: null,
  904. instructionAddr: '0x197885c54',
  905. symbolAddr: '0x197885bf4',
  906. function: '<redacted>',
  907. rawFunction: null,
  908. symbol: null,
  909. context: [],
  910. lineNo: null,
  911. colNo: null,
  912. inApp: false,
  913. trust: null,
  914. errors: null,
  915. vars: null,
  916. },
  917. ],
  918. framesOmitted: null,
  919. registers: {
  920. cpsr: '0x60000000',
  921. fp: '0x16fd79870',
  922. lr: '0x10008c5ac',
  923. },
  924. hasSystemFrames: true,
  925. },
  926. rawStacktrace: null,
  927. },
  928. ],
  929. },
  930. organization: newOrg,
  931. };
  932. const {container} = render(<Threads {...newProps} />, {organization: newOrg});
  933. expect(screen.queryByText('Thread Tags')).not.toBeInTheDocument();
  934. expect(container).toSnapshot();
  935. });
  936. it('maps android vm states to java vm states', function () {
  937. const newEvent = {...event};
  938. const threadsEntry = newEvent.entries[1].data as React.ComponentProps<
  939. typeof Threads
  940. >['data'];
  941. const thread = {
  942. id: 0,
  943. current: false,
  944. crashed: true,
  945. name: 'main',
  946. stacktrace: {
  947. frames: [
  948. {
  949. filename: null,
  950. absPath: null,
  951. module: null,
  952. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  953. platform: null,
  954. instructionAddr: '0x197885c54',
  955. symbolAddr: '0x197885bf4',
  956. function: '<redacted>',
  957. rawFunction: null,
  958. symbol: null,
  959. context: [],
  960. lineNo: null,
  961. colNo: null,
  962. inApp: false,
  963. trust: null,
  964. errors: null,
  965. vars: null,
  966. },
  967. ],
  968. registers: {},
  969. framesOmitted: null,
  970. hasSystemFrames: true,
  971. },
  972. rawStacktrace: null,
  973. state: 'WaitingPerformingGc',
  974. };
  975. threadsEntry.values = [
  976. {
  977. ...thread,
  978. },
  979. {
  980. ...thread,
  981. id: 1,
  982. },
  983. ];
  984. const newOrg = {
  985. ...organization,
  986. features: ['anr-improvements'],
  987. };
  988. const newProps = {...props, event: newEvent, organization: newOrg};
  989. render(<Threads {...newProps} />, {organization: newOrg});
  990. // Title
  991. expect(screen.getByTestId('thread-selector')).toBeInTheDocument();
  992. expect(screen.getByText('Threads')).toBeInTheDocument();
  993. expect(screen.getByText('Thread State')).toBeInTheDocument();
  994. // WaitingPerformingGc maps to Waiting for both Thread tag and Thread State
  995. expect(screen.getByText('Thread Tags')).toBeInTheDocument();
  996. expect(screen.getAllByText('Waiting')).toHaveLength(2);
  997. });
  998. it('toggle full stack trace button', async function () {
  999. render(<Threads {...props} />, {organization});
  1000. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  1001. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).not.toBeChecked();
  1002. await userEvent.click(screen.getByRole('radio', {name: 'Full Stack Trace'}));
  1003. expect(screen.getByRole('radio', {name: 'Full Stack Trace'})).toBeChecked();
  1004. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(4);
  1005. });
  1006. it('toggle sort by option', async function () {
  1007. render(<Threads {...props} />, {organization});
  1008. expect(
  1009. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  1010. '-[SentryClient crash]'
  1011. )
  1012. ).toBeInTheDocument();
  1013. await userEvent.click(screen.getByRole('button', {name: 'Newest'}));
  1014. // Sort by options
  1015. expect(screen.getAllByText('Newest')).toHaveLength(2);
  1016. expect(screen.getByText('Oldest')).toBeInTheDocument();
  1017. // Recent first is checked by default
  1018. expect(screen.getByRole('option', {name: 'Newest'})).toHaveAttribute(
  1019. 'aria-selected',
  1020. 'true'
  1021. );
  1022. // Click on recent last
  1023. await userEvent.click(screen.getByText('Oldest'));
  1024. // Recent last is enabled
  1025. expect(screen.queryByText('Newest')).not.toBeInTheDocument();
  1026. expect(screen.getByText('Oldest')).toBeInTheDocument();
  1027. // Last frame is the first on the list
  1028. expect(
  1029. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText('UIKit')
  1030. ).toBeInTheDocument();
  1031. // Switch back to recent first
  1032. await userEvent.click(screen.getByRole('button', {name: 'Oldest'}));
  1033. await userEvent.click(screen.getByText('Newest'));
  1034. // First frame is the first on the list
  1035. expect(
  1036. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  1037. '-[SentryClient crash]'
  1038. )
  1039. ).toBeInTheDocument();
  1040. });
  1041. it('check display options', async function () {
  1042. render(<Threads {...props} />, {organization});
  1043. await userEvent.click(screen.getByRole('button', {name: 'Options'}));
  1044. expect(await screen.findByText('Display')).toBeInTheDocument();
  1045. Object.values(displayOptions).forEach(value => {
  1046. expect(screen.getByText(value)).toBeInTheDocument();
  1047. });
  1048. // Hover over absolute file paths option
  1049. await userEvent.hover(screen.getByText(displayOptions['absolute-file-paths']));
  1050. // Absolute file paths option is disabled
  1051. expect(
  1052. await screen.findByText('Absolute file paths not available')
  1053. ).toBeInTheDocument();
  1054. // Hover over Minified option
  1055. await userEvent.hover(screen.getByText(displayOptions.minified));
  1056. // Minified option is disabled
  1057. expect(
  1058. await screen.findByText('Unsymbolicated version not available')
  1059. ).toBeInTheDocument();
  1060. // Function name is not verbose
  1061. expect(
  1062. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  1063. 'ViewController.causeCrash'
  1064. )
  1065. ).toBeInTheDocument();
  1066. // Click on verbose function name option
  1067. await userEvent.click(screen.getByText(displayOptions['verbose-function-names']));
  1068. // Function name is now verbose
  1069. expect(
  1070. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  1071. 'ViewController.causeCrash(Any) -> ()'
  1072. )
  1073. ).toBeInTheDocument();
  1074. // Address is not absolute
  1075. expect(
  1076. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('+0x085ac')
  1077. ).toBeInTheDocument();
  1078. // Click on absolute file paths option
  1079. await userEvent.click(screen.getByText(displayOptions['absolute-addresses']));
  1080. // Address is now absolute
  1081. expect(
  1082. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('0x10008c5ac')
  1083. ).toBeInTheDocument();
  1084. MockApiClient.addMockResponse({
  1085. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/apple-crash-report?minified=false`,
  1086. body: '',
  1087. });
  1088. // Click on raw stack trace option
  1089. await userEvent.click(screen.getByText(displayOptions['raw-stack-trace']));
  1090. // Download button is displayed
  1091. expect(screen.getByRole('button', {name: 'Download'})).toBeInTheDocument();
  1092. // Full stack trace toggler is not displayed
  1093. expect(
  1094. screen.queryByRole('radio', {name: 'Full Stack Trace'})
  1095. ).not.toBeInTheDocument();
  1096. // Raw content is displayed
  1097. expect(screen.queryByRole('list', {name: 'Stack trace'})).not.toBeInTheDocument();
  1098. // Raw content and the Raw stack trace option
  1099. expect(screen.getAllByTestId('raw-stack-trace')).toHaveLength(2);
  1100. // Raw stack trace option
  1101. expect(screen.getByRole('option', {name: 'Raw stack trace'})).toBeInTheDocument();
  1102. });
  1103. it('uses thread label in selector if name not available', function () {
  1104. const newEvent = {...event};
  1105. const threadsEntry = newEvent.entries[1].data as React.ComponentProps<
  1106. typeof Threads
  1107. >['data'];
  1108. const thread = {
  1109. id: 0,
  1110. current: false,
  1111. crashed: true,
  1112. name: null,
  1113. stacktrace: {
  1114. frames: [
  1115. {
  1116. filename: null,
  1117. absPath: null,
  1118. module: null,
  1119. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  1120. platform: null,
  1121. instructionAddr: '0x197885c54',
  1122. symbolAddr: '0x197885bf4',
  1123. function: '<redacted>',
  1124. rawFunction: null,
  1125. symbol: null,
  1126. context: [],
  1127. lineNo: null,
  1128. colNo: null,
  1129. inApp: false,
  1130. trust: null,
  1131. errors: null,
  1132. vars: null,
  1133. },
  1134. ],
  1135. registers: {},
  1136. framesOmitted: null,
  1137. hasSystemFrames: true,
  1138. },
  1139. rawStacktrace: null,
  1140. };
  1141. threadsEntry.values = [
  1142. {
  1143. ...thread,
  1144. },
  1145. {
  1146. ...thread,
  1147. id: 1,
  1148. },
  1149. ];
  1150. const newProps = {...props, event: newEvent};
  1151. render(<Threads {...newProps} />, {organization});
  1152. // Title
  1153. const threadSelector = screen.getByTestId('thread-selector');
  1154. expect(threadSelector).toBeInTheDocument();
  1155. within(threadSelector).getByText('ViewController.causeCrash');
  1156. });
  1157. });
  1158. });
  1159. });