test_minidump_full.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import zipfile
  2. from io import BytesIO
  3. from unittest.mock import patch
  4. import pytest
  5. from django.core.files.uploadedfile import SimpleUploadedFile
  6. from django.urls import reverse
  7. from sentry import eventstore
  8. from sentry.lang.native.utils import STORE_CRASH_REPORTS_ALL
  9. from sentry.models.eventattachment import EventAttachment
  10. from sentry.testutils.cases import TransactionTestCase
  11. from sentry.testutils.factories import get_fixture_path
  12. from sentry.testutils.helpers.task_runner import BurstTaskRunner
  13. from sentry.testutils.relay import RelayStoreHelper
  14. from sentry.testutils.skips import requires_kafka, requires_symbolicator
  15. from sentry.utils.safe import get_path
  16. from tests.symbolicator import insta_snapshot_native_stacktrace_data, redact_location
  17. # IMPORTANT:
  18. #
  19. # This test suite requires Symbolicator in order to run correctly.
  20. # Set `symbolicator.enabled: true` in your `~/.sentry/config.yml` and run `sentry devservices up`
  21. #
  22. # If you are using a local instance of Symbolicator, you need to
  23. # either change `system.url-prefix` option override inside `initialize` fixture to `system.internal-url-prefix`,
  24. # or add `127.0.0.1 host.docker.internal` entry to your `/etc/hosts`
  25. pytestmark = [requires_symbolicator, requires_kafka]
  26. @pytest.mark.snuba
  27. class SymbolicatorMinidumpIntegrationTest(RelayStoreHelper, TransactionTestCase):
  28. @pytest.fixture(autouse=True)
  29. def initialize(self, live_server, reset_snuba):
  30. self.project.update_option("sentry:builtin_symbol_sources", [])
  31. with (
  32. patch("sentry.auth.system.is_internal_ip", return_value=True),
  33. self.options({"system.url-prefix": live_server.url}),
  34. ):
  35. # Run test case
  36. yield
  37. def upload_symbols(self):
  38. url = reverse(
  39. "sentry-api-0-dsym-files",
  40. kwargs={
  41. "organization_id_or_slug": self.project.organization.slug,
  42. "project_id_or_slug": self.project.slug,
  43. },
  44. )
  45. self.login_as(user=self.user)
  46. out = BytesIO()
  47. f = zipfile.ZipFile(out, "w")
  48. f.write(get_fixture_path("native", "windows.sym"), "crash.sym")
  49. f.close()
  50. response = self.client.post(
  51. url,
  52. {
  53. "file": SimpleUploadedFile(
  54. "symbols.zip", out.getvalue(), content_type="application/zip"
  55. )
  56. },
  57. format="multipart",
  58. )
  59. assert response.status_code == 201, response.content
  60. assert len(response.json()) == 1
  61. _FEATURES = {
  62. "organizations:event-attachments": True,
  63. "organizations:symbol-sources": False,
  64. "organizations:custom-symbol-sources": False,
  65. }
  66. def test_full_minidump(self):
  67. self.project.update_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_ALL)
  68. self.upload_symbols()
  69. with self.feature(self._FEATURES):
  70. with open(get_fixture_path("native", "windows.dmp"), "rb") as f:
  71. event = self.post_and_retrieve_minidump(
  72. {
  73. "upload_file_minidump": f,
  74. "some_file": ("hello.txt", BytesIO(b"Hello World!")),
  75. },
  76. {
  77. "sentry[logger]": "test-logger",
  78. "sentry[level]": "error",
  79. },
  80. )
  81. candidates = event.data["debug_meta"]["images"][0]["candidates"]
  82. redact_location(candidates)
  83. event.data["debug_meta"]["images"][0]["candidates"] = candidates
  84. insta_snapshot_native_stacktrace_data(self, event.data)
  85. assert event.data.get("logger") == "test-logger"
  86. assert event.data.get("level") == "error"
  87. # assert event.data.get("extra") == {"foo": "bar"}
  88. attachments = sorted(
  89. EventAttachment.objects.filter(event_id=event.event_id), key=lambda x: x.name
  90. )
  91. hello, minidump = attachments
  92. assert hello.name == "hello.txt"
  93. assert hello.sha1 == "2ef7bde608ce5404e97d5f042f95f89f1c232871"
  94. assert minidump.name == "windows.dmp"
  95. assert minidump.sha1 == "74bb01c850e8d65d3ffbc5bad5cabc4668fce247"
  96. def test_full_minidump_json_extra(self):
  97. self.project.update_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_ALL)
  98. self.upload_symbols()
  99. with self.feature("organizations:event-attachments"):
  100. with open(get_fixture_path("native", "windows.dmp"), "rb") as f:
  101. event = self.post_and_retrieve_minidump(
  102. {"upload_file_minidump": f},
  103. {"sentry": '{"logger":"test-logger"}', "foo": "bar"},
  104. )
  105. assert event.data.get("logger") == "test-logger"
  106. assert event.data.get("extra") == {"foo": "bar"}
  107. # Other assertions are performed by `test_full_minidump`
  108. def test_full_minidump_invalid_extra(self):
  109. self.project.update_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_ALL)
  110. self.upload_symbols()
  111. with self.feature("organizations:event-attachments"):
  112. with open(get_fixture_path("native", "windows.dmp"), "rb") as f:
  113. event = self.post_and_retrieve_minidump(
  114. {"upload_file_minidump": f},
  115. {"sentry": "{{{{", "foo": "bar"}, # invalid sentry JSON
  116. )
  117. assert not event.data.get("logger")
  118. assert event.data.get("extra") == {"foo": "bar"}
  119. # Other assertions are performed by `test_full_minidump`
  120. def test_missing_dsym(self):
  121. with self.feature(self._FEATURES):
  122. with open(get_fixture_path("native", "windows.dmp"), "rb") as f:
  123. event = self.post_and_retrieve_minidump(
  124. {"upload_file_minidump": f}, {"sentry[logger]": "test-logger"}
  125. )
  126. insta_snapshot_native_stacktrace_data(self, event.data)
  127. assert not EventAttachment.objects.filter(event_id=event.event_id)
  128. def test_reprocessing(self):
  129. # NOTE:
  130. # When running this test against a local symbolicator instance,
  131. # make sure that instance has its caches disabled. This test assumes
  132. # that a symbol upload has immediate effect, whereas in reality the
  133. # negative cache needs to expire first.
  134. self.project.update_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_ALL)
  135. with self.feature(self._FEATURES):
  136. with open(get_fixture_path("native", "windows.dmp"), "rb") as f:
  137. event = self.post_and_retrieve_minidump(
  138. {"upload_file_minidump": f}, {"sentry[logger]": "test-logger"}
  139. )
  140. insta_snapshot_native_stacktrace_data(self, event.data, subname="initial")
  141. self.upload_symbols()
  142. from sentry.tasks.reprocessing2 import reprocess_group
  143. with BurstTaskRunner() as burst:
  144. reprocess_group.delay(project_id=self.project.id, group_id=event.group_id)
  145. burst(max_jobs=100)
  146. new_event = eventstore.backend.get_event_by_id(self.project.id, event.event_id)
  147. assert new_event is not None
  148. assert new_event.event_id == event.event_id
  149. candidates = new_event.data["debug_meta"]["images"][0]["candidates"]
  150. redact_location(candidates)
  151. new_event.data["debug_meta"]["images"][0]["candidates"] = candidates
  152. insta_snapshot_native_stacktrace_data(self, new_event.data, subname="reprocessed")
  153. for event_id in (event.event_id, new_event.event_id):
  154. (minidump,) = sorted(
  155. EventAttachment.objects.filter(event_id=new_event.event_id), key=lambda x: x.name
  156. )
  157. assert minidump.name == "windows.dmp"
  158. assert minidump.sha1 == "74bb01c850e8d65d3ffbc5bad5cabc4668fce247"
  159. def test_minidump_threadnames(self):
  160. self.project.update_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_ALL)
  161. with self.feature(self._FEATURES):
  162. with open(get_fixture_path("native", "threadnames.dmp"), "rb") as f:
  163. event = self.post_and_retrieve_minidump({"upload_file_minidump": f}, {})
  164. thread_name = get_path(event.data, "threads", "values", 1, "name")
  165. assert thread_name == "sentry-http"