threads.spec.tsx 49 KB

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