test_payload_full.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. import os.path
  2. import zipfile
  3. from io import BytesIO
  4. from unittest.mock import patch
  5. from uuid import uuid4
  6. import pytest
  7. from django.core.files.uploadedfile import SimpleUploadedFile
  8. from django.urls import reverse
  9. from sentry import eventstore
  10. from sentry.models.artifactbundle import (
  11. ArtifactBundle,
  12. DebugIdArtifactBundle,
  13. ProjectArtifactBundle,
  14. SourceFileType,
  15. )
  16. from sentry.models.debugfile import ProjectDebugFile
  17. from sentry.models.files.file import File
  18. from sentry.testutils.cases import TransactionTestCase
  19. from sentry.testutils.factories import get_fixture_path
  20. from sentry.testutils.helpers.datetime import before_now, iso_format
  21. from sentry.testutils.relay import RelayStoreHelper
  22. from sentry.testutils.skips import requires_kafka, requires_symbolicator
  23. from sentry.utils import json
  24. from tests.symbolicator import insta_snapshot_native_stacktrace_data, redact_location
  25. # IMPORTANT:
  26. #
  27. # This test suite requires Symbolicator in order to run correctly.
  28. # Set `symbolicator.enabled: true` in your `~/.sentry/config.yml` and run `sentry devservices up`
  29. #
  30. # If you are using a local instance of Symbolicator, you need to
  31. # either change `system.url-prefix` option override inside `initialize` fixture to `system.internal-url-prefix`,
  32. # or add `127.0.0.1 host.docker.internal` entry to your `/etc/hosts`
  33. pytestmark = [requires_symbolicator, requires_kafka]
  34. REAL_RESOLVING_EVENT_DATA = {
  35. "platform": "cocoa",
  36. "debug_meta": {
  37. "images": [
  38. {
  39. "type": "apple",
  40. "arch": "x86_64",
  41. "uuid": "502fc0a5-1ec1-3e47-9998-684fa139dca7",
  42. "image_vmaddr": "0x0000000100000000",
  43. "image_size": 4096,
  44. "image_addr": "0x0000000100000000",
  45. "name": "Foo.app/Contents/Foo",
  46. }
  47. ],
  48. "sdk_info": {
  49. "dsym_type": "macho",
  50. "sdk_name": "macOS",
  51. "version_major": 10,
  52. "version_minor": 12,
  53. "version_patchlevel": 4,
  54. },
  55. },
  56. "exception": {
  57. "values": [
  58. {
  59. "stacktrace": {
  60. "frames": [
  61. {"platform": "foobar", "function": "hi"},
  62. {"function": "unknown", "instruction_addr": "0x0000000100000fa0"},
  63. ]
  64. },
  65. "type": "Fail",
  66. "value": "fail",
  67. }
  68. ]
  69. },
  70. "timestamp": iso_format(before_now(seconds=1)),
  71. }
  72. def get_local_fixture_path(name):
  73. return os.path.join(os.path.dirname(__file__), "fixtures", name)
  74. def load_fixture(name):
  75. with open(get_local_fixture_path(name), "rb") as fp:
  76. return fp.read()
  77. class SymbolicatorResolvingIntegrationTest(RelayStoreHelper, TransactionTestCase):
  78. @pytest.fixture(autouse=True)
  79. def initialize(self, live_server):
  80. self.project.update_option("sentry:builtin_symbol_sources", [])
  81. self.min_ago = iso_format(before_now(minutes=1))
  82. with patch("sentry.auth.system.is_internal_ip", return_value=True), self.options(
  83. {"system.url-prefix": live_server.url}
  84. ):
  85. # Run test case
  86. yield
  87. def get_event(self, event_id):
  88. return eventstore.backend.get_event_by_id(self.project.id, event_id)
  89. def test_real_resolving(self):
  90. url = reverse(
  91. "sentry-api-0-dsym-files",
  92. kwargs={
  93. "organization_id_or_slug": self.project.organization.slug,
  94. "project_id_or_slug": self.project.slug,
  95. },
  96. )
  97. self.login_as(user=self.user)
  98. out = BytesIO()
  99. f = zipfile.ZipFile(out, "w")
  100. f.write(get_fixture_path("native", "hello.dsym"), "dSYM/hello")
  101. f.close()
  102. response = self.client.post(
  103. url,
  104. {
  105. "file": SimpleUploadedFile(
  106. "symbols.zip", out.getvalue(), content_type="application/zip"
  107. )
  108. },
  109. format="multipart",
  110. )
  111. assert response.status_code == 201, response.content
  112. assert len(response.json()) == 1
  113. event = self.post_and_retrieve_event(REAL_RESOLVING_EVENT_DATA)
  114. assert event.data["culprit"] == "main"
  115. candidates = event.data["debug_meta"]["images"][0]["candidates"]
  116. redact_location(candidates)
  117. event.data["debug_meta"]["images"][0]["candidates"] = candidates
  118. insta_snapshot_native_stacktrace_data(self, event.data)
  119. def test_debug_id_resolving(self):
  120. file = File.objects.create(
  121. name="crash.pdb", type="default", headers={"Content-Type": "text/x-breakpad"}
  122. )
  123. path = get_fixture_path("native", "windows.sym")
  124. with open(path, "rb") as f:
  125. file.putfile(f)
  126. ProjectDebugFile.objects.create(
  127. file=file,
  128. object_name="crash.pdb",
  129. cpu_name="x86",
  130. project_id=self.project.id,
  131. debug_id="3249d99d-0c40-4931-8610-f4e4fb0b6936-1",
  132. code_id="5AB380779000",
  133. )
  134. self.login_as(user=self.user)
  135. event_data = {
  136. "contexts": {
  137. "device": {"arch": "x86"},
  138. "os": {"build": "", "name": "Windows", "type": "os", "version": "10.0.14393"},
  139. },
  140. "debug_meta": {
  141. "images": [
  142. {
  143. "id": "3249d99d-0c40-4931-8610-f4e4fb0b6936-1",
  144. "image_addr": "0x2a0000",
  145. "image_size": 36864,
  146. "name": "C:\\projects\\breakpad-tools\\windows\\Release\\crash.exe",
  147. "type": "symbolic",
  148. }
  149. ]
  150. },
  151. "exception": {
  152. "stacktrace": {
  153. "frames": [
  154. {
  155. "function": "<unknown>",
  156. "instruction_addr": "0x2a2a3d",
  157. "package": "C:\\projects\\breakpad-tools\\windows\\Release\\crash.exe",
  158. }
  159. ]
  160. },
  161. "thread_id": 1636,
  162. "type": "EXCEPTION_ACCESS_VIOLATION_WRITE",
  163. "value": "Fatal Error: EXCEPTION_ACCESS_VIOLATION_WRITE",
  164. },
  165. "platform": "native",
  166. "timestamp": iso_format(before_now(seconds=1)),
  167. }
  168. event = self.post_and_retrieve_event(event_data)
  169. assert event.data["culprit"] == "main"
  170. candidates = event.data["debug_meta"]["images"][0]["candidates"]
  171. redact_location(candidates)
  172. event.data["debug_meta"]["images"][0]["candidates"] = candidates
  173. insta_snapshot_native_stacktrace_data(self, event.data)
  174. def test_missing_dsym(self):
  175. self.login_as(user=self.user)
  176. event = self.post_and_retrieve_event(REAL_RESOLVING_EVENT_DATA)
  177. assert event.data["culprit"] == "unknown"
  178. insta_snapshot_native_stacktrace_data(self, event.data)
  179. def test_missing_debug_images(self):
  180. self.login_as(user=self.user)
  181. payload = dict(project=self.project.id, **REAL_RESOLVING_EVENT_DATA)
  182. del payload["debug_meta"]
  183. event = self.post_and_retrieve_event(payload)
  184. assert event.data["culprit"] == "unknown"
  185. insta_snapshot_native_stacktrace_data(self, event.data)
  186. def test_resolving_with_candidates_sentry_source(self):
  187. # Checks the candidates with a sentry source URI for location
  188. file = File.objects.create(
  189. name="crash.pdb", type="default", headers={"Content-Type": "text/x-breakpad"}
  190. )
  191. path = get_fixture_path("native", "windows.sym")
  192. with open(path, "rb") as f:
  193. file.putfile(f)
  194. ProjectDebugFile.objects.create(
  195. file=file,
  196. object_name="crash.pdb",
  197. cpu_name="x86",
  198. project_id=self.project.id,
  199. debug_id="3249d99d-0c40-4931-8610-f4e4fb0b6936-1",
  200. code_id="5AB380779000",
  201. )
  202. self.login_as(user=self.user)
  203. event_data = {
  204. "contexts": {
  205. "device": {"arch": "x86"},
  206. },
  207. "debug_meta": {
  208. "images": [
  209. {
  210. "id": "3249d99d-0c40-4931-8610-f4e4fb0b6936-1",
  211. "image_addr": "0x2a0000",
  212. "image_size": 36864,
  213. "name": "C:\\projects\\breakpad-tools\\windows\\Release\\crash.exe",
  214. "type": "symbolic",
  215. }
  216. ]
  217. },
  218. "exception": {
  219. "stacktrace": {
  220. "frames": [
  221. {
  222. "instruction_addr": "0x2a2a3d",
  223. }
  224. ]
  225. },
  226. "type": "EXCEPTION_ACCESS_VIOLATION_WRITE",
  227. "value": "Fatal Error: EXCEPTION_ACCESS_VIOLATION_WRITE",
  228. },
  229. "platform": "native",
  230. "timestamp": iso_format(before_now(seconds=1)),
  231. }
  232. event = self.post_and_retrieve_event(event_data)
  233. assert event.data["culprit"] == "main"
  234. candidates = event.data["debug_meta"]["images"][0]["candidates"]
  235. redact_location(candidates)
  236. self.insta_snapshot(candidates)
  237. def test_resolve_mixed_stack_trace(self):
  238. # JS debug files:
  239. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  240. compressed = BytesIO(b"SYSB")
  241. with zipfile.ZipFile(compressed, "a") as zip_file:
  242. zip_file.writestr("files/_/_/test.min.js", load_fixture("test.min.js"))
  243. zip_file.writestr("files/_/_/test.map", load_fixture("test.map"))
  244. zip_file.writestr(
  245. "manifest.json",
  246. json.dumps(
  247. {
  248. "files": {
  249. "files/_/_/test.min.js": {
  250. "url": "~/test.min.js",
  251. "type": "minified_source",
  252. "headers": {
  253. "debug-id": debug_id,
  254. "sourcemap": "test.map",
  255. },
  256. },
  257. "files/_/_/test.map": {
  258. "url": "~/file.wc.sourcemap.js",
  259. "type": "source_map",
  260. "headers": {
  261. "debug-id": debug_id,
  262. },
  263. },
  264. },
  265. }
  266. ),
  267. )
  268. compressed.seek(0)
  269. bundle_file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  270. bundle_file.putfile(compressed)
  271. artifact_bundle = ArtifactBundle.objects.create(
  272. organization_id=self.organization.id,
  273. bundle_id=uuid4(),
  274. file=bundle_file,
  275. artifact_count=2,
  276. )
  277. ProjectArtifactBundle.objects.create(
  278. organization_id=self.organization.id,
  279. project_id=self.project.id,
  280. artifact_bundle=artifact_bundle,
  281. )
  282. DebugIdArtifactBundle.objects.create(
  283. organization_id=self.organization.id,
  284. debug_id=debug_id,
  285. artifact_bundle=artifact_bundle,
  286. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  287. )
  288. DebugIdArtifactBundle.objects.create(
  289. organization_id=self.organization.id,
  290. debug_id=debug_id,
  291. artifact_bundle=artifact_bundle,
  292. source_file_type=SourceFileType.SOURCE_MAP.value,
  293. )
  294. # native debug files:
  295. wasm_file = File.objects.create(
  296. name="test.wasm", type="default", headers={"Content-Type": "application/wasm"}
  297. )
  298. with open(get_local_fixture_path("a18fd85d4a4eb893022d6bfad846b1.debug"), "rb") as f:
  299. wasm_file.putfile(f)
  300. ProjectDebugFile.objects.create(
  301. file=wasm_file,
  302. object_name="test.wasm",
  303. cpu_name="wasm32",
  304. project_id=self.project.id,
  305. debug_id="bda18fd8-5d4a-4eb8-9302-2d6bfad846b1",
  306. code_id="bda18fd85d4a4eb893022d6bfad846b1",
  307. )
  308. data = {
  309. "timestamp": self.min_ago,
  310. "message": "hello",
  311. "platform": "javascript",
  312. "release": "abc",
  313. "exception": {
  314. "values": [
  315. {
  316. "type": "Error",
  317. "stacktrace": {
  318. "frames": [
  319. {
  320. "abs_path": "http://example.com/test.min.js",
  321. "lineno": 1,
  322. "colno": 183,
  323. },
  324. {
  325. "platform": "native",
  326. "instruction_addr": "0x8c",
  327. "addr_mode": "rel:0",
  328. },
  329. ]
  330. },
  331. }
  332. ]
  333. },
  334. "debug_meta": {
  335. "images": [
  336. {
  337. "type": "sourcemap",
  338. "debug_id": debug_id,
  339. "code_file": "http://example.com/test.min.js",
  340. },
  341. {
  342. "type": "wasm",
  343. "debug_id": "bda18fd8-5d4a-4eb8-9302-2d6bfad846b1",
  344. "code_id": "bda18fd85d4a4eb893022d6bfad846b1",
  345. "debug_file": "file://foo.invalid/demo.wasm",
  346. },
  347. ]
  348. },
  349. }
  350. event = self.post_and_retrieve_event(data)
  351. exception = event.interfaces["exception"]
  352. frames = exception.values[0].stacktrace.frames
  353. assert frames[0].abs_path == "http://example.com/test.js"
  354. assert frames[0].lineno == 20
  355. assert frames[0].colno == 5
  356. assert frames[0].context_line == " invoke(data);"
  357. assert frames[1].abs_path == "/Users/mitsuhiko/Development/wasm-example/simple/src/lib.rs"
  358. assert frames[1].lineno == 19
  359. assert frames[1].function == "internal_func"
  360. images = event.data["debug_meta"]["images"]
  361. assert images[1]["debug_status"] == "found"