test_task.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. from __future__ import annotations
  2. import zipfile
  3. from io import BytesIO
  4. from os.path import join
  5. from tempfile import TemporaryFile
  6. from typing import Any
  7. import pytest
  8. from django.core.files.uploadedfile import SimpleUploadedFile
  9. from django.urls import reverse
  10. from sentry.lang.javascript.processing import _handles_frame as is_valid_javascript_frame
  11. from sentry.models.files.file import File
  12. from sentry.models.project import Project
  13. from sentry.models.projectkey import ProjectKey, UseCase
  14. from sentry.models.release import Release
  15. from sentry.models.releasefile import ReleaseFile
  16. from sentry.profiles.task import (
  17. Profile,
  18. _calculate_profile_duration_ms,
  19. _deobfuscate,
  20. _deobfuscate_locally,
  21. _deobfuscate_using_symbolicator,
  22. _normalize,
  23. _process_symbolicator_results_for_sample,
  24. _set_frames_platform,
  25. _symbolicate_profile,
  26. get_metrics_dsn,
  27. )
  28. from sentry.testutils.cases import TransactionTestCase
  29. from sentry.testutils.factories import Factories, get_fixture_path
  30. from sentry.testutils.pytest.fixtures import django_db_all
  31. from sentry.testutils.skips import requires_symbolicator
  32. from sentry.utils import json
  33. PROFILES_FIXTURES_PATH = get_fixture_path("profiles")
  34. PROGUARD_UUID = "6dc7fdb0-d2fb-4c8e-9d6b-bb1aa98929b1"
  35. PROGUARD_SOURCE = b"""\
  36. # compiler: R8
  37. # compiler_version: 2.0.74
  38. # min_api: 16
  39. # pg_map_id: 5b46fdc
  40. # common_typos_disable
  41. # {"id":"com.android.tools.r8.mapping","version":"1.0"}
  42. org.slf4j.helpers.Util$ClassContextSecurityManager -> org.a.b.g$a:
  43. 65:65:void <init>() -> <init>
  44. 67:67:java.lang.Class[] getClassContext() -> a
  45. 69:69:java.lang.Class[] getExtraClassContext() -> a
  46. 65:65:void <init>(org.slf4j.helpers.Util$1) -> <init>
  47. """
  48. PROGUARD_INLINE_UUID = "d748e578-b3d1-5be5-b0e5-a42e8c9bf8e0"
  49. PROGUARD_INLINE_SOURCE = b"""\
  50. # compiler: R8
  51. # compiler_version: 2.0.74
  52. # min_api: 16
  53. # pg_map_id: 5b46fdc
  54. # common_typos_disable
  55. # {"id":"com.android.tools.r8.mapping","version":"1.0"}
  56. $r8$backportedMethods$utility$Objects$2$equals -> a:
  57. boolean equals(java.lang.Object,java.lang.Object) -> a
  58. $r8$twr$utility -> b:
  59. void $closeResource(java.lang.Throwable,java.lang.Object) -> a
  60. android.support.v4.app.RemoteActionCompatParcelizer -> android.support.v4.app.RemoteActionCompatParcelizer:
  61. 1:1:void <init>():11:11 -> <init>
  62. io.sentry.sample.-$$Lambda$r3Avcbztes2hicEObh02jjhQqd4 -> e.a.c.a:
  63. io.sentry.sample.MainActivity f$0 -> b
  64. io.sentry.sample.MainActivity -> io.sentry.sample.MainActivity:
  65. 1:1:void <init>():15:15 -> <init>
  66. 1:1:boolean onCreateOptionsMenu(android.view.Menu):60:60 -> onCreateOptionsMenu
  67. 1:1:boolean onOptionsItemSelected(android.view.MenuItem):69:69 -> onOptionsItemSelected
  68. 2:2:boolean onOptionsItemSelected(android.view.MenuItem):76:76 -> onOptionsItemSelected
  69. 1:1:void bar():54:54 -> t
  70. 1:1:void foo():44 -> t
  71. 1:1:void onClickHandler(android.view.View):40 -> t
  72. """
  73. PROGUARD_BUG_UUID = "071207ac-b491-4a74-957c-2c94fd9594f2"
  74. PROGUARD_BUG_SOURCE = b"x"
  75. def load_profile(name):
  76. path = join(PROFILES_FIXTURES_PATH, name)
  77. with open(path) as f:
  78. return json.loads(f.read())
  79. def load_proguard(project, proguard_uuid, proguard_source):
  80. with TemporaryFile() as tf:
  81. tf.write(proguard_source)
  82. tf.seek(0)
  83. file = Factories.create_file(
  84. name=proguard_uuid,
  85. type="project.dif",
  86. headers={"Content-Type": "proguard"},
  87. )
  88. file.putfile(tf)
  89. return Factories.create_dif_file(
  90. project,
  91. file=file,
  92. debug_id=proguard_uuid,
  93. object_name="proguard-mapping",
  94. data={"features": ["mapping"]},
  95. )
  96. @pytest.fixture
  97. def owner():
  98. return Factories.create_user()
  99. @pytest.fixture
  100. def organization(owner):
  101. return Factories.create_organization(owner=owner)
  102. @pytest.fixture
  103. def team(organization, owner):
  104. team = Factories.create_team(organization=organization)
  105. Factories.create_team_membership(team=team, user=owner)
  106. return team
  107. @pytest.fixture
  108. def project(organization, team):
  109. return Factories.create_project(organization=organization, teams=[team])
  110. @pytest.fixture
  111. def ios_profile():
  112. return load_profile("valid_ios_profile.json")
  113. @pytest.fixture
  114. def android_profile():
  115. return load_profile("valid_android_profile.json")
  116. @pytest.fixture
  117. def sample_v1_profile():
  118. return json.loads(
  119. """{
  120. "event_id": "41fed0925670468bb0457f61a74688ec",
  121. "version": "1",
  122. "os": {
  123. "name": "iOS",
  124. "version": "16.0",
  125. "build_number": "19H253"
  126. },
  127. "device": {
  128. "architecture": "arm64e",
  129. "is_emulator": false,
  130. "locale": "en_US",
  131. "manufacturer": "Apple",
  132. "model": "iPhone14,3"
  133. },
  134. "timestamp": "2022-09-01T09:45:00.000Z",
  135. "profile": {
  136. "samples": [
  137. {
  138. "stack_id": 0,
  139. "thread_id": "1",
  140. "queue_address": "0x0000000102adc700",
  141. "elapsed_since_start_ns": "10500500"
  142. },
  143. {
  144. "stack_id": 1,
  145. "thread_id": "1",
  146. "queue_address": "0x0000000102adc700",
  147. "elapsed_since_start_ns": "20500500"
  148. },
  149. {
  150. "stack_id": 0,
  151. "thread_id": "1",
  152. "queue_address": "0x0000000102adc700",
  153. "elapsed_since_start_ns": "30500500"
  154. },
  155. {
  156. "stack_id": 1,
  157. "thread_id": "1",
  158. "queue_address": "0x0000000102adc700",
  159. "elapsed_since_start_ns": "35500500"
  160. }
  161. ],
  162. "stacks": [[0], [1]],
  163. "frames": [
  164. {"instruction_addr": "0xa722447ffffffffc"},
  165. {"instruction_addr": "0x442e4b81f5031e58"}
  166. ],
  167. "thread_metadata": {
  168. "1": {"priority": 31},
  169. "2": {}
  170. },
  171. "queue_metadata": {
  172. "0x0000000102adc700": {"label": "com.apple.main-thread"},
  173. "0x000000016d8fb180": {"label": "com.apple.network.connections"}
  174. }
  175. },
  176. "release": "0.1 (199)",
  177. "platform": "cocoa",
  178. "debug_meta": {
  179. "images": [
  180. {
  181. "debug_id": "32420279-25E2-34E6-8BC7-8A006A8F2425",
  182. "image_addr": "0x000000010258c000",
  183. "code_file": "/private/var/containers/Bundle/Application/C3511752-DD67-4FE8-9DA2-ACE18ADFAA61/TrendingMovies.app/TrendingMovies",
  184. "type": "macho",
  185. "image_size": 1720320,
  186. "image_vmaddr": "0x0000000100000000"
  187. }
  188. ]
  189. },
  190. "transaction": {
  191. "name": "example_ios_movies_sources.MoviesViewController",
  192. "trace_id": "4b25bc58f14243d8b208d1e22a054164",
  193. "id": "30976f2ddbe04ac9b6bffe6e35d4710c",
  194. "active_thread_id": "259",
  195. "relative_start_ns": "500500",
  196. "relative_end_ns": "50500500"
  197. }
  198. }"""
  199. )
  200. @pytest.fixture
  201. def sample_v1_profile_without_transaction_timestamps(sample_v1_profile):
  202. for key in {"relative_start_ns", "relative_end_ns"}:
  203. del sample_v1_profile["transaction"][key]
  204. return sample_v1_profile
  205. @pytest.fixture
  206. def sample_v2_profile():
  207. return json.loads(
  208. """{
  209. "event_id": "41fed0925670468bb0457f61a74688ec",
  210. "version": "2",
  211. "profile": {
  212. "samples": [
  213. {
  214. "stack_id": 0,
  215. "thread_id": "1",
  216. "timestamp": 1710958503.629
  217. },
  218. {
  219. "stack_id": 1,
  220. "thread_id": "1",
  221. "timestamp": 1710958504.629
  222. },
  223. {
  224. "stack_id": 0,
  225. "thread_id": "1",
  226. "timestamp": 1710958505.629
  227. },
  228. {
  229. "stack_id": 1,
  230. "thread_id": "1",
  231. "timestamp": 1710958506.629
  232. }
  233. ],
  234. "stacks": [[0], [1]],
  235. "frames": [
  236. {"instruction_addr": "0xa722447ffffffffc"},
  237. {"instruction_addr": "0x442e4b81f5031e58"}
  238. ],
  239. "thread_metadata": {
  240. "1": {"priority": 31},
  241. "2": {}
  242. }
  243. },
  244. "release": "0.1 (199)",
  245. "platform": "cocoa",
  246. "debug_meta": {
  247. "images": [
  248. {
  249. "debug_id": "32420279-25E2-34E6-8BC7-8A006A8F2425",
  250. "image_addr": "0x000000010258c000",
  251. "code_file": "/private/var/containers/Bundle/Application/C3511752-DD67-4FE8-9DA2-ACE18ADFAA61/TrendingMovies.app/TrendingMovies",
  252. "type": "macho",
  253. "image_size": 1720320,
  254. "image_vmaddr": "0x0000000100000000"
  255. }
  256. ]
  257. }
  258. }"""
  259. )
  260. @pytest.fixture
  261. def proguard_file_basic(project):
  262. return load_proguard(project, PROGUARD_UUID, PROGUARD_SOURCE)
  263. @pytest.fixture
  264. def proguard_file_inline(project):
  265. return load_proguard(project, PROGUARD_INLINE_UUID, PROGUARD_INLINE_SOURCE)
  266. @pytest.fixture
  267. def proguard_file_bug(project):
  268. return load_proguard(project, PROGUARD_BUG_UUID, PROGUARD_BUG_SOURCE)
  269. @django_db_all
  270. def test_normalize_ios_profile(organization, ios_profile):
  271. _normalize(profile=ios_profile, organization=organization)
  272. for k in ["device_os_build_number", "device_classification"]:
  273. assert k in ios_profile
  274. @django_db_all
  275. def test_normalize_android_profile(organization, android_profile):
  276. _normalize(profile=android_profile, organization=organization)
  277. for k in ["android_api_level", "device_classification"]:
  278. assert k in android_profile
  279. assert isinstance(android_profile["android_api_level"], int)
  280. @django_db_all
  281. def test_basic_deobfuscation(project, proguard_file_basic, android_profile):
  282. android_profile.update(
  283. {
  284. "build_id": PROGUARD_UUID,
  285. "project_id": project.id,
  286. "profile": {
  287. "methods": [
  288. {
  289. "abs_path": None,
  290. "class_name": "org.a.b.g$a",
  291. "name": "a",
  292. "signature": "()V",
  293. "source_file": None,
  294. "source_line": 67,
  295. },
  296. {
  297. "abs_path": None,
  298. "class_name": "org.a.b.g$a",
  299. "name": "a",
  300. "signature": "()V",
  301. "source_file": None,
  302. "source_line": 69,
  303. },
  304. ],
  305. },
  306. }
  307. )
  308. _deobfuscate_locally(android_profile, project, PROGUARD_UUID)
  309. frames = android_profile["profile"]["methods"]
  310. assert frames[0]["name"] == "getClassContext"
  311. assert frames[0]["class_name"] == "org.slf4j.helpers.Util$ClassContextSecurityManager"
  312. assert frames[1]["name"] == "getExtraClassContext"
  313. assert frames[1]["class_name"] == "org.slf4j.helpers.Util$ClassContextSecurityManager"
  314. @django_db_all
  315. def test_inline_deobfuscation(project, proguard_file_inline, android_profile):
  316. android_profile.update(
  317. {
  318. "build_id": PROGUARD_INLINE_UUID,
  319. "project_id": project.id,
  320. "profile": {
  321. "methods": [
  322. {
  323. "abs_path": None,
  324. "class_name": "e.a.c.a",
  325. "name": "onClick",
  326. "signature": "()V",
  327. "source_file": None,
  328. "source_line": 2,
  329. },
  330. {
  331. "abs_path": None,
  332. "class_name": "io.sentry.sample.MainActivity",
  333. "name": "t",
  334. "signature": "()V",
  335. "source_file": "MainActivity.java",
  336. "source_line": 1,
  337. },
  338. ],
  339. },
  340. }
  341. )
  342. project = Project.objects.get_from_cache(id=android_profile["project_id"])
  343. _deobfuscate_locally(android_profile, project, PROGUARD_INLINE_UUID)
  344. frames = android_profile["profile"]["methods"]
  345. assert sum(len(f.get("inline_frames", [])) for f in frames) == 3
  346. assert frames[0]["name"] == "onClick"
  347. assert frames[0]["class_name"] == "io.sentry.sample.-$$Lambda$r3Avcbztes2hicEObh02jjhQqd4"
  348. assert frames[1]["inline_frames"][0]["name"] == "onClickHandler"
  349. assert frames[1]["inline_frames"][0]["source_line"] == 40
  350. assert frames[1]["inline_frames"][0]["source_file"] == "MainActivity.java"
  351. assert frames[1]["inline_frames"][0]["class_name"] == "io.sentry.sample.MainActivity"
  352. assert frames[1]["inline_frames"][0]["signature"] == "()"
  353. assert frames[1]["inline_frames"][1]["name"] == "foo"
  354. assert frames[1]["inline_frames"][1]["source_line"] == 44
  355. assert frames[1]["inline_frames"][2]["source_file"] == "MainActivity.java"
  356. assert frames[1]["inline_frames"][2]["class_name"] == "io.sentry.sample.MainActivity"
  357. assert frames[1]["inline_frames"][2]["name"] == "bar"
  358. assert frames[1]["inline_frames"][2]["source_line"] == 54
  359. @django_db_all
  360. def test_error_on_resolving(project, proguard_file_bug, android_profile):
  361. android_profile.update(
  362. {
  363. "build_id": PROGUARD_BUG_UUID,
  364. "project_id": project.id,
  365. "profile": {
  366. "methods": [
  367. {
  368. "name": "a",
  369. "abs_path": None,
  370. "class_name": "org.a.b.g$a",
  371. "source_file": None,
  372. "source_line": 67,
  373. },
  374. {
  375. "name": "a",
  376. "abs_path": None,
  377. "class_name": "org.a.b.g$a",
  378. "source_file": None,
  379. "source_line": 69,
  380. },
  381. ],
  382. },
  383. }
  384. )
  385. project = Project.objects.get_from_cache(id=android_profile["project_id"])
  386. obfuscated_frames = android_profile["profile"]["methods"].copy()
  387. _deobfuscate(android_profile, project)
  388. assert android_profile["profile"]["methods"] == obfuscated_frames
  389. def test_process_symbolicator_results_for_sample():
  390. profile: dict[str, Any] = {
  391. "version": 1,
  392. "platform": "rust",
  393. "profile": {
  394. "frames": [
  395. {
  396. "instruction_addr": "0x55bd050e168d",
  397. "lang": "rust",
  398. "sym_addr": "0x55bd050e1590",
  399. },
  400. {
  401. "instruction_addr": "0x89bf050e178a",
  402. "lang": "rust",
  403. "sym_addr": "0x95bc050e2530",
  404. },
  405. {
  406. "instruction_addr": "0x88ad050d167e",
  407. "lang": "rust",
  408. "sym_addr": "0x29cd050a1642",
  409. },
  410. ],
  411. "samples": [
  412. {"stack_id": 0},
  413. # a second sample with the same stack id, the stack should
  414. # not be processed a second time
  415. {"stack_id": 0},
  416. ],
  417. "stacks": [
  418. [0, 1, 2],
  419. ],
  420. },
  421. }
  422. # returned from symbolicator
  423. stacktraces = [
  424. {
  425. "frames": [
  426. {
  427. "instruction_addr": "0x72ba053e168c",
  428. "lang": "rust",
  429. "function": "C_inline_1",
  430. "original_index": 0,
  431. },
  432. {
  433. "instruction_addr": "0x55bd050e168d",
  434. "lang": "rust",
  435. "function": "C",
  436. "sym_addr": "0x55bd050e1590",
  437. "original_index": 0,
  438. },
  439. {
  440. "instruction_addr": "0x89bf050e178a",
  441. "lang": "rust",
  442. "function": "B",
  443. "sym_addr": "0x95bc050e2530",
  444. "original_index": 1,
  445. },
  446. {
  447. "instruction_addr": "0x68fd050d127b",
  448. "lang": "rust",
  449. "function": "A_inline_1",
  450. "original_index": 2,
  451. },
  452. {
  453. "instruction_addr": "0x29ce061d168a",
  454. "lang": "rust",
  455. "function": "A_inline_2",
  456. "original_index": 2,
  457. },
  458. {
  459. "instruction_addr": "0x88ad050d167e",
  460. "lang": "rust",
  461. "function": "A",
  462. "sym_addr": "0x29cd050a1642",
  463. "original_index": 2,
  464. },
  465. ],
  466. },
  467. ]
  468. _process_symbolicator_results_for_sample(
  469. profile, stacktraces, set(range(len(profile["profile"]["frames"]))), profile["platform"]
  470. )
  471. assert profile["profile"]["stacks"] == [[0, 1, 2, 3, 4, 5]]
  472. def test_process_symbolicator_results_for_sample_js():
  473. profile: dict[str, Any] = {
  474. "version": 1,
  475. "platform": "javascript",
  476. "profile": {
  477. "frames": [
  478. {
  479. "function": "functionA",
  480. "abs_path": "/root/functionA.js",
  481. },
  482. {
  483. "function": "functionB",
  484. "abs_path": "/root/functionB.js",
  485. },
  486. {
  487. "function": "functionC",
  488. "abs_path": "/root/functionC.js",
  489. },
  490. # frame not valid for symbolication
  491. {
  492. "function": "functionD",
  493. },
  494. ],
  495. "samples": [
  496. {"stack_id": 0},
  497. # a second sample with the same stack id, the stack should
  498. # not be processed a second time
  499. {"stack_id": 0},
  500. ],
  501. "stacks": [
  502. [0, 1, 2, 3],
  503. ],
  504. },
  505. }
  506. # returned from symbolicator
  507. stacktraces = [
  508. {
  509. "frames": [
  510. {
  511. "function": "functionA",
  512. "abs_path": "/root/functionA.js",
  513. "original_index": 0,
  514. },
  515. {
  516. "function": "functionB",
  517. "abs_path": "/root/functionB.js",
  518. "original_index": 1,
  519. },
  520. {
  521. "function": "functionC",
  522. "abs_path": "/root/functionC.js",
  523. "original_index": 2,
  524. },
  525. ],
  526. },
  527. ]
  528. frames_sent = [
  529. idx
  530. for idx, frame in enumerate(profile["profile"]["frames"])
  531. if is_valid_javascript_frame(frame, profile)
  532. ]
  533. _process_symbolicator_results_for_sample(
  534. profile, stacktraces, set(frames_sent), profile["platform"]
  535. )
  536. assert profile["profile"]["stacks"] == [[0, 1, 2, 3]]
  537. @django_db_all
  538. def test_decode_signature(project, android_profile):
  539. android_profile.update(
  540. {
  541. "project_id": project.id,
  542. "profile": {
  543. "methods": [
  544. {
  545. "abs_path": None,
  546. "class_name": "org.a.b.g$a",
  547. "name": "a",
  548. "signature": "()V",
  549. "source_file": None,
  550. "source_line": 67,
  551. },
  552. {
  553. "abs_path": None,
  554. "class_name": "org.a.b.g$a",
  555. "name": "a",
  556. "signature": "()Z",
  557. "source_file": None,
  558. "source_line": 69,
  559. },
  560. ],
  561. },
  562. }
  563. )
  564. _deobfuscate(android_profile, project)
  565. frames = android_profile["profile"]["methods"]
  566. assert frames[0]["signature"] == "()"
  567. assert frames[1]["signature"] == "(): boolean"
  568. @django_db_all
  569. @pytest.mark.parametrize(
  570. "profile, duration_ms",
  571. [
  572. ("sample_v1_profile", 50),
  573. ("sample_v2_profile", 3000),
  574. ("android_profile", 2020),
  575. ("sample_v1_profile_without_transaction_timestamps", 25),
  576. ],
  577. )
  578. def test_calculate_profile_duration(profile, duration_ms, request):
  579. assert _calculate_profile_duration_ms(request.getfixturevalue(profile)) == duration_ms
  580. @pytest.mark.django_db(transaction=True)
  581. class DeobfuscationViaSymbolicator(TransactionTestCase):
  582. @pytest.fixture(autouse=True)
  583. def initialize(self, set_sentry_option, live_server):
  584. with set_sentry_option("system.url-prefix", live_server.url):
  585. # Run test case
  586. yield
  587. def upload_proguard_mapping(self, uuid, mapping_file_content):
  588. url = reverse(
  589. "sentry-api-0-dsym-files",
  590. kwargs={
  591. "organization_id_or_slug": self.project.organization.slug,
  592. "project_id_or_slug": self.project.slug,
  593. },
  594. )
  595. self.login_as(user=self.user)
  596. out = BytesIO()
  597. f = zipfile.ZipFile(out, "w")
  598. f.writestr("proguard/%s.txt" % uuid, mapping_file_content)
  599. f.writestr("ignored-file.txt", b"This is just some stuff")
  600. f.close()
  601. response = self.client.post(
  602. url,
  603. {
  604. "file": SimpleUploadedFile(
  605. "symbols.zip", out.getvalue(), content_type="application/zip"
  606. )
  607. },
  608. format="multipart",
  609. )
  610. assert response.status_code == 201, response.content
  611. assert len(response.json()) == 1
  612. @requires_symbolicator
  613. @pytest.mark.symbolicator
  614. def test_basic_resolving(self):
  615. self.upload_proguard_mapping(PROGUARD_UUID, PROGUARD_SOURCE)
  616. android_profile = load_profile("valid_android_profile.json")
  617. android_profile.update(
  618. {
  619. "project_id": self.project.id,
  620. "build_id": PROGUARD_UUID,
  621. "event_id": android_profile["profile_id"],
  622. "profile": {
  623. "methods": [
  624. {
  625. "class_name": "org.a.b.g$a",
  626. "name": "a",
  627. "signature": "()V",
  628. "source_file": "Something.java",
  629. "source_line": 67,
  630. },
  631. {
  632. "class_name": "org.a.b.g$a",
  633. "name": "a",
  634. "signature": "()Z",
  635. "source_file": "Else.java",
  636. "source_line": 69,
  637. },
  638. ],
  639. },
  640. }
  641. )
  642. _deobfuscate_using_symbolicator(
  643. self.project,
  644. android_profile,
  645. PROGUARD_UUID,
  646. )
  647. assert android_profile["profile"]["methods"] == [
  648. {
  649. "data": {"deobfuscation_status": "deobfuscated"},
  650. "name": "getClassContext",
  651. "class_name": "org.slf4j.helpers.Util$ClassContextSecurityManager",
  652. "signature": "()",
  653. "source_file": "Something.java",
  654. "source_line": 67,
  655. },
  656. {
  657. "data": {"deobfuscation_status": "deobfuscated"},
  658. "name": "getExtraClassContext",
  659. "class_name": "org.slf4j.helpers.Util$ClassContextSecurityManager",
  660. "signature": "(): boolean",
  661. "source_file": "Else.java",
  662. "source_line": 69,
  663. },
  664. ]
  665. @requires_symbolicator
  666. @pytest.mark.symbolicator
  667. def test_inline_resolving(self):
  668. self.upload_proguard_mapping(PROGUARD_INLINE_UUID, PROGUARD_INLINE_SOURCE)
  669. android_profile = load_profile("valid_android_profile.json")
  670. android_profile.update(
  671. {
  672. "project_id": self.project.id,
  673. "build_id": PROGUARD_INLINE_UUID,
  674. "event_id": android_profile["profile_id"],
  675. "profile": {
  676. "methods": [
  677. {
  678. "class_name": "e.a.c.a",
  679. "name": "onClick",
  680. "signature": "()V",
  681. "source_file": None,
  682. "source_line": 2,
  683. },
  684. {
  685. "class_name": "io.sentry.sample.MainActivity",
  686. "name": "t",
  687. "signature": "()V",
  688. "source_file": "MainActivity.java",
  689. "source_line": 1,
  690. },
  691. ],
  692. },
  693. }
  694. )
  695. _deobfuscate_using_symbolicator(
  696. self.project,
  697. android_profile,
  698. PROGUARD_INLINE_UUID,
  699. )
  700. assert android_profile["profile"]["methods"] == [
  701. {
  702. "class_name": "io.sentry.sample.-$$Lambda$r3Avcbztes2hicEObh02jjhQqd4",
  703. "data": {
  704. "deobfuscation_status": "deobfuscated",
  705. },
  706. "name": "onClick",
  707. "signature": "()",
  708. "source_file": None,
  709. "source_line": 2,
  710. },
  711. {
  712. "class_name": "io.sentry.sample.MainActivity",
  713. "data": {
  714. "deobfuscation_status": "deobfuscated",
  715. },
  716. "inline_frames": [
  717. {
  718. "class_name": "io.sentry.sample.MainActivity",
  719. "data": {
  720. "deobfuscation_status": "deobfuscated",
  721. },
  722. "name": "onClickHandler",
  723. "signature": "()",
  724. "source_file": "MainActivity.java",
  725. "source_line": 40,
  726. },
  727. {
  728. "class_name": "io.sentry.sample.MainActivity",
  729. "data": {
  730. "deobfuscation_status": "deobfuscated",
  731. },
  732. "name": "foo",
  733. "signature": "()",
  734. "source_file": "MainActivity.java",
  735. "source_line": 44,
  736. },
  737. {
  738. "class_name": "io.sentry.sample.MainActivity",
  739. "data": {
  740. "deobfuscation_status": "deobfuscated",
  741. },
  742. "name": "bar",
  743. "signature": "()",
  744. "source_file": "MainActivity.java",
  745. "source_line": 54,
  746. },
  747. ],
  748. "name": "onClickHandler",
  749. "signature": "()",
  750. "source_file": "MainActivity.java",
  751. "source_line": 40,
  752. },
  753. ]
  754. @requires_symbolicator
  755. @pytest.mark.symbolicator
  756. def test_js_symbolication_set_symbolicated_field(self):
  757. release = Release.objects.create(
  758. organization_id=self.project.organization_id, version="nodeprof123"
  759. )
  760. release.add_project(self.project)
  761. for file in ["embedded.js", "embedded.js.map"]:
  762. with open(get_fixture_path(f"profiles/{file}"), "rb") as f:
  763. f1 = File.objects.create(
  764. name=file,
  765. type="release.file",
  766. headers={},
  767. )
  768. f1.putfile(f)
  769. ReleaseFile.objects.create(
  770. name=f"http://example.com/{f1.name}",
  771. release_id=release.id,
  772. organization_id=self.project.organization_id,
  773. file=f1,
  774. )
  775. js_profile = load_profile("valid_js_profile.json")
  776. js_profile.update(
  777. {
  778. "project_id": self.project.id,
  779. "event_id": js_profile["profile_id"],
  780. "release": release.version,
  781. "debug_meta": {"images": []},
  782. }
  783. )
  784. _symbolicate_profile(js_profile, self.project)
  785. assert js_profile["profile"]["frames"][0].get("data", {}).get("symbolicated", False)
  786. def test_set_frames_platform_sample():
  787. js_prof: Profile = {
  788. "version": "1",
  789. "platform": "javascript",
  790. "profile": {
  791. "frames": [
  792. {"function": "a"},
  793. {"function": "b", "platform": "cocoa"},
  794. {"function": "c"},
  795. ]
  796. },
  797. }
  798. _set_frames_platform(js_prof)
  799. platforms = [f["platform"] for f in js_prof["profile"]["frames"]]
  800. assert platforms == ["javascript", "cocoa", "javascript"]
  801. def test_set_frames_platform_android():
  802. android_prof: Profile = {
  803. "platform": "android",
  804. "profile": {
  805. "methods": [
  806. {"name": "a"},
  807. {"name": "b"},
  808. ]
  809. },
  810. }
  811. _set_frames_platform(android_prof)
  812. platforms = [m["platform"] for m in android_prof["profile"]["methods"]]
  813. assert platforms == ["android", "android"]
  814. @django_db_all
  815. def test_get_metrics_dsn(default_project):
  816. key1 = ProjectKey.objects.create(project=default_project, use_case=UseCase.PROFILING.value)
  817. ProjectKey.objects.create(project_id=default_project.id, use_case=UseCase.PROFILING.value)
  818. assert get_metrics_dsn(default_project.id) == key1.get_dsn(public=True)