threads.spec.tsx 48 KB

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