threads.spec.tsx 48 KB

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