test_stacktraces.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import pytest
  2. from sentry.grouping.api import get_default_grouping_config_dict, load_grouping_config
  3. from sentry.stacktraces.processing import (
  4. find_stacktraces_in_data,
  5. get_crash_frame_from_event_data,
  6. normalize_stacktraces_for_grouping,
  7. )
  8. from sentry.testutils import TestCase
  9. class FindStacktracesTest(TestCase):
  10. def test_stacktraces_basics(self):
  11. data = {
  12. "message": "hello",
  13. "platform": "javascript",
  14. "stacktrace": {
  15. "frames": [
  16. {
  17. "abs_path": "http://example.com/foo.js",
  18. "filename": "foo.js",
  19. "lineno": 4,
  20. "colno": 0,
  21. },
  22. {
  23. "abs_path": "http://example.com/foo.js",
  24. "filename": "foo.js",
  25. "lineno": 1,
  26. "colno": 0,
  27. "platform": "native",
  28. },
  29. ]
  30. },
  31. }
  32. infos = find_stacktraces_in_data(data)
  33. assert len(infos) == 1
  34. assert len(infos[0].stacktrace["frames"]) == 2
  35. assert infos[0].platforms == {"javascript", "native"}
  36. def test_stacktraces_exception(self):
  37. data = {
  38. "message": "hello",
  39. "platform": "javascript",
  40. "exception": {
  41. "values": [
  42. {
  43. "type": "Error",
  44. "stacktrace": {
  45. "frames": [
  46. {
  47. "abs_path": "http://example.com/foo.js",
  48. "filename": "foo.js",
  49. "lineno": 4,
  50. "colno": 0,
  51. },
  52. {
  53. "abs_path": "http://example.com/foo.js",
  54. "filename": "foo.js",
  55. "lineno": 1,
  56. "colno": 0,
  57. },
  58. ]
  59. },
  60. }
  61. ]
  62. },
  63. }
  64. infos = find_stacktraces_in_data(data)
  65. assert len(infos) == 1
  66. assert len(infos[0].stacktrace["frames"]) == 2
  67. def test_stacktraces_threads(self):
  68. data = {
  69. "message": "hello",
  70. "platform": "javascript",
  71. "threads": {
  72. "values": [
  73. {
  74. "id": "4711",
  75. "stacktrace": {
  76. "frames": [
  77. {
  78. "abs_path": "http://example.com/foo.js",
  79. "filename": "foo.js",
  80. "lineno": 4,
  81. "colno": 0,
  82. },
  83. {
  84. "abs_path": "http://example.com/foo.js",
  85. "filename": "foo.js",
  86. "lineno": 1,
  87. "colno": 0,
  88. },
  89. ]
  90. },
  91. }
  92. ]
  93. },
  94. }
  95. infos = find_stacktraces_in_data(data)
  96. assert len(infos) == 1
  97. assert len(infos[0].stacktrace["frames"]) == 2
  98. def test_find_stacktraces_skip_none(self):
  99. # This tests:
  100. # 1. exception is None
  101. # 2. stacktrace is None
  102. # 3. frames is None
  103. # 3. frames contains only None
  104. # 4. frame is None
  105. data = {
  106. "message": "hello",
  107. "platform": "javascript",
  108. "exception": {
  109. "values": [
  110. None,
  111. {"type": "Error", "stacktrace": None},
  112. {"type": "Error", "stacktrace": {"frames": None}},
  113. {"type": "Error", "stacktrace": {"frames": [None]}},
  114. {
  115. "type": "Error",
  116. "stacktrace": {
  117. "frames": [
  118. None,
  119. {
  120. "abs_path": "http://example.com/foo.js",
  121. "filename": "foo.js",
  122. "lineno": 4,
  123. "colno": 0,
  124. },
  125. {
  126. "abs_path": "http://example.com/foo.js",
  127. "filename": "foo.js",
  128. "lineno": 1,
  129. "colno": 0,
  130. },
  131. ]
  132. },
  133. },
  134. ]
  135. },
  136. }
  137. infos = find_stacktraces_in_data(data, with_exceptions=True)
  138. assert len(infos) == 4
  139. assert sum(1 for x in infos if x.stacktrace) == 3
  140. assert sum(1 for x in infos if x.is_exception) == 4
  141. # XXX: The null frame is still part of this stack trace!
  142. assert len(infos[3].stacktrace["frames"]) == 3
  143. infos = find_stacktraces_in_data(data)
  144. assert len(infos) == 1
  145. # XXX: The null frame is still part of this stack trace!
  146. assert len(infos[0].stacktrace["frames"]) == 3
  147. class NormalizeInApptest(TestCase):
  148. def test_normalize_with_system_frames(self):
  149. data = {
  150. "stacktrace": {
  151. "frames": [
  152. None,
  153. {
  154. "abs_path": "http://example.com/foo.js",
  155. "filename": "foo.js",
  156. "lineno": 4,
  157. "colno": 0,
  158. "in_app": True,
  159. },
  160. {
  161. "abs_path": "http://example.com/foo.js",
  162. "filename": "foo.js",
  163. "lineno": 1,
  164. "colno": 0,
  165. },
  166. ]
  167. }
  168. }
  169. normalize_stacktraces_for_grouping(data)
  170. assert data["stacktrace"]["frames"][1]["in_app"] is True
  171. assert data["stacktrace"]["frames"][2]["in_app"] is False
  172. def test_normalize_skips_none(self):
  173. data = {
  174. "stacktrace": {
  175. "frames": [
  176. None,
  177. {
  178. "abs_path": "http://example.com/foo.js",
  179. "filename": "foo.js",
  180. "lineno": 4,
  181. "colno": 0,
  182. },
  183. {
  184. "abs_path": "http://example.com/foo.js",
  185. "filename": "foo.js",
  186. "lineno": 1,
  187. "colno": 0,
  188. },
  189. ]
  190. }
  191. }
  192. normalize_stacktraces_for_grouping(data)
  193. assert data["stacktrace"]["frames"][1]["in_app"] is False
  194. assert data["stacktrace"]["frames"][2]["in_app"] is False
  195. def test_ios_package_in_app_detection(self):
  196. data = {
  197. "platform": "native",
  198. "stacktrace": {
  199. "frames": [
  200. {
  201. "package": "/var/containers/Bundle/Application/B33C37A8-F933-4B6B-9FFA-152282BFDF13/SentryTest.app/SentryTest",
  202. "instruction_addr": "0x1000",
  203. },
  204. {
  205. "package": "/var/containers/Bundle/Application/B33C37A8-F933-4B6B-9FFA-152282BFDF13/SentryTest.app/Frameworks/foo.dylib",
  206. "instruction_addr": "0x2000",
  207. },
  208. {
  209. "package": "/var/containers/Bundle/Application/B33C37A8-F933-4B6B-9FFA-152282BFDF13/SentryTest.app/Frameworks/libswiftCore.dylib",
  210. "instruction_addr": "0x3000",
  211. },
  212. {"package": "/usr/lib/whatever.dylib", "instruction_addr": "0x4000"},
  213. ]
  214. },
  215. }
  216. config = load_grouping_config(get_default_grouping_config_dict())
  217. normalize_stacktraces_for_grouping(data, grouping_config=config)
  218. # App object should be in_app
  219. assert data["stacktrace"]["frames"][0]["in_app"] is True
  220. # Framework should be in app (but optional)
  221. assert data["stacktrace"]["frames"][1]["in_app"] is True
  222. # libswift should not be system
  223. assert data["stacktrace"]["frames"][2]["in_app"] is False
  224. # Unknown object should default to not in_app
  225. assert data["stacktrace"]["frames"][3]["in_app"] is False
  226. def tes_macos_package_in_app_detection(self):
  227. data = {
  228. "platform": "cocoa",
  229. "debug_meta": {"images": []}, # omitted
  230. "exception": {
  231. "values": [
  232. {
  233. "stacktrace": {
  234. "frames": [
  235. {
  236. "function": "-[CRLCrashAsyncSafeThread crash]",
  237. "package": "/Users/haza/Library/Developer/Xcode/Archives/2017-06-19/CrashProbe 19-06-2017, 08.53.xcarchive/Products/Applications/CrashProbe.app/Contents/Frameworks/CrashLib.framework/Versions/A/CrashLib",
  238. "instruction_addr": 4295098388,
  239. },
  240. {
  241. "function": "[KSCrash ]",
  242. "package": "/usr/lib/system/libdyld.dylib",
  243. "instruction_addr": 4295098388,
  244. },
  245. ]
  246. },
  247. "type": "NSRangeException",
  248. }
  249. ]
  250. },
  251. "contexts": {"os": {"version": "10.12.5", "type": "os", "name": "macOS"}},
  252. }
  253. config = load_grouping_config(get_default_grouping_config_dict())
  254. normalize_stacktraces_for_grouping(data, grouping_config=config)
  255. frames = data["exception"]["values"][0]["stacktrace"]["frames"]
  256. assert frames[0]["in_app"] is True
  257. assert frames[1]["in_app"] is False
  258. def ios_function_name_in_app_detection(self, function, isInApp: bool):
  259. data = {
  260. "platform": "cocoa",
  261. "debug_meta": {"images": []}, # omitted
  262. "exception": {
  263. "values": [
  264. {
  265. "stacktrace": {
  266. "frames": [
  267. {
  268. "function": function,
  269. "package": "/var/containers/Bundle/Application/B33C37A8-F933-4B6B-9FFA-152282BFDF13/SentryTest.app/SentryTest",
  270. "instruction_addr": 4295098388,
  271. },
  272. # We need two frames otherwise all frames are inApp
  273. {
  274. "function": "[KSCrash ]",
  275. "package": "/usr/lib/system/libdyld.dylib",
  276. "instruction_addr": 4295098388,
  277. },
  278. ]
  279. },
  280. "type": "NSRangeException",
  281. }
  282. ]
  283. },
  284. "contexts": {"os": {"version": "9.3.2", "type": "os", "name": "iOS"}},
  285. }
  286. config = load_grouping_config(get_default_grouping_config_dict())
  287. normalize_stacktraces_for_grouping(data, grouping_config=config)
  288. frames = data["exception"]["values"][0]["stacktrace"]["frames"]
  289. assert frames[0]["in_app"] is isInApp, (
  290. "For function: " + function + " expected:" + str(isInApp)
  291. )
  292. def test_ios_function_name_in_app_detection(self):
  293. self.ios_function_name_in_app_detection(
  294. function="sentrycrash__hook_dispatch_async", isInApp=False
  295. )
  296. self.ios_function_name_in_app_detection(
  297. function="sentrycrash__hook_dispatch_after_f", isInApp=False
  298. )
  299. self.ios_function_name_in_app_detection(
  300. function="sentrycrash__async_backtrace_capture", isInApp=False
  301. )
  302. self.ios_function_name_in_app_detection(
  303. function="__sentrycrash__hook_dispatch_async_block_invoke", isInApp=False
  304. )
  305. self.ios_function_name_in_app_detection(function="kscm_f", isInApp=False)
  306. self.ios_function_name_in_app_detection(function="kscm_", isInApp=False)
  307. self.ios_function_name_in_app_detection(function=" kscm_", isInApp=False)
  308. self.ios_function_name_in_app_detection(function="kscm", isInApp=True)
  309. self.ios_function_name_in_app_detection(function="sentrycrashcm_f", isInApp=False)
  310. self.ios_function_name_in_app_detection(function="sentrycrashcm_", isInApp=False)
  311. self.ios_function_name_in_app_detection(function=" sentrycrashcm_", isInApp=False)
  312. self.ios_function_name_in_app_detection(function="sentrycrashcm", isInApp=True)
  313. self.ios_function_name_in_app_detection(function="kscrash_f", isInApp=False)
  314. self.ios_function_name_in_app_detection(function="kscrash_", isInApp=False)
  315. self.ios_function_name_in_app_detection(function=" kscrash_", isInApp=False)
  316. self.ios_function_name_in_app_detection(function="kscrash", isInApp=True)
  317. self.ios_function_name_in_app_detection(function="sentrycrash_f", isInApp=False)
  318. self.ios_function_name_in_app_detection(function="sentrycrash_", isInApp=False)
  319. self.ios_function_name_in_app_detection(function=" sentrycrash_", isInApp=False)
  320. self.ios_function_name_in_app_detection(function="sentrycrash", isInApp=True)
  321. self.ios_function_name_in_app_detection(function="+[KSCrash ]", isInApp=False)
  322. self.ios_function_name_in_app_detection(function="-[KSCrash]", isInApp=False)
  323. self.ios_function_name_in_app_detection(function="-[KSCrashy]", isInApp=False)
  324. self.ios_function_name_in_app_detection(function="-[MyKSCrashy ", isInApp=True)
  325. self.ios_function_name_in_app_detection(function="+[RNSentry ]", isInApp=False)
  326. self.ios_function_name_in_app_detection(function="-[RNSentry]", isInApp=False)
  327. self.ios_function_name_in_app_detection(function="-[RNSentry]", isInApp=False)
  328. self.ios_function_name_in_app_detection(function="-[MRNSentry ]", isInApp=True)
  329. self.ios_function_name_in_app_detection(function="+[Sentry ]", isInApp=False)
  330. self.ios_function_name_in_app_detection(function="-[Sentry]", isInApp=False)
  331. self.ios_function_name_in_app_detection(function="-[Sentry]", isInApp=False)
  332. self.ios_function_name_in_app_detection(function="-[MSentry capture]", isInApp=True)
  333. self.ios_function_name_in_app_detection(
  334. function="-[SentryHub captureMessage]", isInApp=False
  335. )
  336. self.ios_function_name_in_app_detection(
  337. function="-[SentryClient captureMessage]", isInApp=False
  338. )
  339. self.ios_function_name_in_app_detection(
  340. function="+[SentrySDK captureMessage]", isInApp=False
  341. )
  342. self.ios_function_name_in_app_detection(
  343. function="-[SentryStacktraceBuilder buildStacktraceForCurrentThread]", isInApp=False
  344. )
  345. self.ios_function_name_in_app_detection(
  346. function="-[SentryThreadInspector getCurrentThreads]", isInApp=False
  347. )
  348. self.ios_function_name_in_app_detection(
  349. function="-[SentryClient captureMessage:withScope:]", isInApp=False
  350. )
  351. self.ios_function_name_in_app_detection(
  352. function="-[SentryFrameInAppLogic isInApp:]", isInApp=False
  353. )
  354. self.ios_function_name_in_app_detection(
  355. function="-[SentryFrameRemover removeNonSdkFrames:]", isInApp=False
  356. )
  357. self.ios_function_name_in_app_detection(
  358. function="-[SentryDebugMetaBuilder buildDebugMeta:withScope:]", isInApp=False
  359. )
  360. self.ios_function_name_in_app_detection(
  361. function="-[SentryCrashAdapter crashedLastLaunch]", isInApp=False
  362. )
  363. self.ios_function_name_in_app_detection(
  364. function="-[SentryCrashAdapter isRateLimitActive:]", isInApp=False
  365. )
  366. self.ios_function_name_in_app_detection(
  367. function="-[SentryTransport sendEvent:attachments:]", isInApp=False
  368. )
  369. self.ios_function_name_in_app_detection(
  370. function="-[SentryHttpTransport sendEvent:attachments:]", isInApp=False
  371. )
  372. @pytest.mark.parametrize(
  373. "event",
  374. [
  375. {"threads": {"values": [{"stacktrace": {"frames": [{"in_app": True, "marco": "polo"}]}}]}},
  376. {
  377. "exception": {
  378. "values": [{"stacktrace": {"frames": [{"in_app": True, "marco": "polo"}]}}]
  379. }
  380. },
  381. {"stacktrace": {"frames": [{"in_app": True, "marco": "polo"}]}},
  382. ],
  383. )
  384. def test_get_crash_frame(event):
  385. assert get_crash_frame_from_event_data(event)["marco"] == "polo"