threads.spec.tsx 48 KB

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