test_plugin.py 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483
  1. import os.path
  2. import zipfile
  3. from base64 import b64encode
  4. from hashlib import sha1
  5. from io import BytesIO
  6. from uuid import uuid4
  7. import pytest
  8. import responses
  9. from django.conf import settings
  10. from django.core.files.base import ContentFile
  11. from sentry.models.artifactbundle import (
  12. ArtifactBundle,
  13. DebugIdArtifactBundle,
  14. ProjectArtifactBundle,
  15. ReleaseArtifactBundle,
  16. SourceFileType,
  17. )
  18. from sentry.models.files.file import File
  19. from sentry.models.files.fileblob import FileBlob
  20. from sentry.models.release import Release
  21. from sentry.models.releasefile import ReleaseFile, update_artifact_index
  22. from sentry.tasks.assemble import assemble_artifacts
  23. from sentry.testutils.helpers.datetime import before_now
  24. from sentry.testutils.pytest.fixtures import django_db_all
  25. from sentry.testutils.relay import RelayStoreHelper
  26. from sentry.testutils.skips import requires_kafka, requires_symbolicator
  27. from sentry.utils import json
  28. from sentry.utils.safe import get_path
  29. # IMPORTANT:
  30. #
  31. # This test suite requires Symbolicator in order to run correctly.
  32. # Set `symbolicator.enabled: true` in your `~/.sentry/config.yml` and run `sentry devservices up`
  33. #
  34. # If you are using a local instance of Symbolicator, you need to either change `system.url-prefix`
  35. # to `system.internal-url-prefix` inside `initialize` method below, or add `127.0.0.1 host.docker.internal`
  36. # entry to your `/etc/hosts`
  37. pytestmark = [requires_symbolicator, requires_kafka]
  38. BASE64_SOURCEMAP = "data:application/json;base64," + (
  39. b64encode(
  40. b'{"version":3,"file":"generated.js","sources":["/test.js"],"names":[],"mappings":"AAAA","sourcesContent":['
  41. b'"console.log(\\"hello, World!\\")"]}'
  42. )
  43. .decode("utf-8")
  44. .replace("\n", "")
  45. )
  46. INVALID_BASE64_SOURCEMAP = "data:application/json;base64,A"
  47. def get_fixture_path(name):
  48. return os.path.join(os.path.dirname(__file__), "fixtures", name)
  49. def load_fixture(name):
  50. with open(get_fixture_path(name), "rb") as fp:
  51. return fp.read()
  52. def make_compressed_zip_file(files):
  53. def remove_and_return(dictionary, key):
  54. dictionary.pop(key)
  55. return dictionary
  56. compressed = BytesIO(b"SYSB")
  57. with zipfile.ZipFile(compressed, "a") as zip_file:
  58. for file_path, info in files.items():
  59. zip_file.writestr(file_path, bytes(info["content"]))
  60. zip_file.writestr(
  61. "manifest.json",
  62. json.dumps(
  63. {
  64. # We remove the "content" key in the original dict, thus no subsequent calls should be made.
  65. "files": {
  66. file_path: remove_and_return(info, "content")
  67. for file_path, info in files.items()
  68. }
  69. }
  70. ),
  71. )
  72. compressed.seek(0)
  73. return compressed.getvalue()
  74. def upload_bundle(bundle_file, project, release=None, dist=None, upload_as_artifact_bundle=True):
  75. blob1 = FileBlob.from_file(ContentFile(bundle_file))
  76. total_checksum = sha1(bundle_file).hexdigest()
  77. return assemble_artifacts(
  78. org_id=project.organization.id,
  79. project_ids=[project.id],
  80. version=release,
  81. dist=dist,
  82. checksum=total_checksum,
  83. chunks=[blob1.checksum],
  84. upload_as_artifact_bundle=upload_as_artifact_bundle,
  85. )
  86. @django_db_all(transaction=True)
  87. class TestJavascriptIntegration(RelayStoreHelper):
  88. @pytest.fixture(autouse=True)
  89. def initialize(self, default_projectkey, default_project, set_sentry_option, live_server):
  90. self.project = default_project
  91. self.projectkey = default_projectkey
  92. self.organization = self.project.organization
  93. self.min_ago = before_now(minutes=1).isoformat()
  94. # We disable scraping per-test when necessary.
  95. self.project.update_option("sentry:scrape_javascript", True)
  96. with set_sentry_option("system.url-prefix", live_server.url):
  97. # Run test case
  98. yield
  99. @requires_symbolicator
  100. @pytest.mark.symbolicator
  101. def test_adds_contexts_without_device(self):
  102. data = {
  103. "timestamp": self.min_ago,
  104. "message": "hello",
  105. "platform": "javascript",
  106. "request": {
  107. "url": "http://example.com",
  108. "headers": [
  109. [
  110. "User-Agent",
  111. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
  112. "Chrome/28.0.1500.72 Safari/537.36",
  113. ]
  114. ],
  115. },
  116. }
  117. event = self.post_and_retrieve_event(data)
  118. contexts = event.interfaces["contexts"].to_json()
  119. assert contexts.get("os") == {
  120. "os": "Windows 8",
  121. "name": "Windows",
  122. "version": "8",
  123. "type": "os",
  124. }
  125. assert contexts.get("device") is None
  126. @requires_symbolicator
  127. @pytest.mark.symbolicator
  128. def test_adds_contexts_with_device(self):
  129. data = {
  130. "timestamp": self.min_ago,
  131. "message": "hello",
  132. "platform": "javascript",
  133. "request": {
  134. "url": "http://example.com",
  135. "headers": [
  136. [
  137. "User-Agent",
  138. "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SCH-R530U Build/JSS15J) AppleWebKit/534.30 ("
  139. "KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 USCC-R530U",
  140. ]
  141. ],
  142. },
  143. }
  144. event = self.post_and_retrieve_event(data)
  145. contexts = event.interfaces["contexts"].to_json()
  146. assert contexts.get("os") == {
  147. "os": "Android 4.3",
  148. "name": "Android",
  149. "type": "os",
  150. "version": "4.3",
  151. }
  152. assert contexts.get("browser") == {
  153. "browser": "Android 4.3",
  154. "name": "Android",
  155. "type": "browser",
  156. "version": "4.3",
  157. }
  158. assert contexts.get("device") == {
  159. "family": "Samsung SCH-R530U",
  160. "type": "device",
  161. "model": "SCH-R530U",
  162. "name": "Galaxy S3",
  163. "brand": "Samsung",
  164. }
  165. @requires_symbolicator
  166. @pytest.mark.symbolicator
  167. def test_adds_contexts_with_ps4_device(self):
  168. data = {
  169. "timestamp": self.min_ago,
  170. "message": "hello",
  171. "platform": "javascript",
  172. "request": {
  173. "url": "http://example.com",
  174. "headers": [
  175. [
  176. "User-Agent",
  177. "Mozilla/5.0 (PlayStation 4 3.55) AppleWebKit/537.78 (KHTML, like Gecko)",
  178. ]
  179. ],
  180. },
  181. }
  182. event = self.post_and_retrieve_event(data)
  183. contexts = event.interfaces["contexts"].to_json()
  184. assert contexts.get("os") is None
  185. assert contexts.get("browser") is None
  186. assert contexts.get("device") == {
  187. "family": "PlayStation 4",
  188. "type": "device",
  189. "model": "PlayStation 4",
  190. "brand": "Sony",
  191. }
  192. @requires_symbolicator
  193. @pytest.mark.symbolicator
  194. def test_error_message_translations(self):
  195. data = {
  196. "timestamp": self.min_ago,
  197. "message": "hello",
  198. "platform": "javascript",
  199. "logentry": {
  200. "formatted": "ReferenceError: Impossible de d\xe9finir une propri\xe9t\xe9 \xab foo \xbb : objet non "
  201. "extensible"
  202. },
  203. "exception": {
  204. "values": [
  205. {"type": "Error", "value": "P\u0159\xedli\u0161 mnoho soubor\u016f"},
  206. {
  207. "type": "Error",
  208. "value": "foo: wyst\u0105pi\u0142 nieoczekiwany b\u0142\u0105d podczas pr\xf3by uzyskania "
  209. "informacji o metadanych",
  210. },
  211. ]
  212. },
  213. }
  214. event = self.post_and_retrieve_event(data)
  215. message = event.interfaces["logentry"]
  216. assert (
  217. message.formatted
  218. == "ReferenceError: Cannot define property 'foo': object is not extensible"
  219. )
  220. exception = event.interfaces["exception"]
  221. assert exception.values[0].value == "Too many files"
  222. assert (
  223. exception.values[1].value
  224. == "foo: an unexpected failure occurred while trying to obtain metadata information"
  225. )
  226. @requires_symbolicator
  227. @pytest.mark.symbolicator
  228. def test_nonhandled_frames_inapp_normalization(self):
  229. data = {
  230. "timestamp": self.min_ago,
  231. "message": "hello",
  232. "platform": "node",
  233. "exception": {
  234. "values": [
  235. {
  236. "type": "Error",
  237. "stacktrace": {
  238. "frames": [
  239. {
  240. "abs_path": "native",
  241. "lineno": 1,
  242. "colno": 1,
  243. "in_app": True,
  244. },
  245. {
  246. "abs_path": "[native code]",
  247. "lineno": 1,
  248. "colno": 1,
  249. "in_app": True,
  250. },
  251. {
  252. "abs_path": "app://dist/bundle/file.min.js",
  253. "lineno": 1,
  254. "colno": 1,
  255. "in_app": True,
  256. },
  257. ]
  258. },
  259. }
  260. ]
  261. },
  262. }
  263. event = self.post_and_retrieve_event(data)
  264. exception = event.interfaces["exception"]
  265. frame_list = exception.values[0].stacktrace.frames
  266. assert not frame_list[0].in_app # should be overwritten due to `native` abs_path
  267. assert not frame_list[1].in_app # should be overwritten due to `[native code]` abs_path
  268. assert frame_list[2].in_app # should not be touched and retain `in_app: true`
  269. raw_frame_list = exception.values[0].raw_stacktrace.frames
  270. # none of the raw frames should be altered
  271. assert raw_frame_list[0].in_app
  272. assert raw_frame_list[1].in_app
  273. assert raw_frame_list[2].in_app
  274. @requires_symbolicator
  275. @pytest.mark.symbolicator
  276. def test_sourcemap_source_expansion(self):
  277. self.project.update_option("sentry:scrape_javascript", False)
  278. release = Release.objects.create(
  279. organization_id=self.project.organization_id, version="abc"
  280. )
  281. release.add_project(self.project)
  282. for file in ["file.min.js", "file1.js", "file2.js", "file.sourcemap.js"]:
  283. with open(get_fixture_path(file), "rb") as f:
  284. f1 = File.objects.create(
  285. name=file,
  286. type="release.file",
  287. headers={},
  288. )
  289. f1.putfile(f)
  290. ReleaseFile.objects.create(
  291. name=f"http://example.com/{f1.name}",
  292. release_id=release.id,
  293. organization_id=self.project.organization_id,
  294. file=f1,
  295. )
  296. data = {
  297. "timestamp": self.min_ago,
  298. "message": "hello",
  299. "platform": "javascript",
  300. "release": "abc",
  301. "exception": {
  302. "values": [
  303. {
  304. "type": "Error",
  305. "stacktrace": {
  306. "frames": [
  307. {
  308. "abs_path": "http://example.com/file.min.js",
  309. "filename": "file.min.js",
  310. "lineno": 1,
  311. "colno": 39,
  312. },
  313. # NOTE: Intentionally source is not retrieved from this HTML file
  314. {
  315. "function": 'function: "HTMLDocument.<anonymous>"',
  316. "abs_path": "http//example.com/index.html",
  317. "filename": "index.html",
  318. "lineno": 283,
  319. "colno": 17,
  320. "in_app": False,
  321. },
  322. # NOTE: a mixed stack trace with a native frame:
  323. {
  324. "instruction_addr": "0xd10349",
  325. },
  326. ]
  327. },
  328. }
  329. ]
  330. },
  331. }
  332. event = self.post_and_retrieve_event(data)
  333. assert event.data["errors"] == [
  334. {
  335. "type": "js_no_source",
  336. "symbolicator_type": "missing_source",
  337. "url": "http//example.com/index.html",
  338. }
  339. ]
  340. assert event.data["scraping_attempts"] == [
  341. {"status": "not_attempted", "url": "http://example.com/file.min.js"},
  342. {"status": "not_attempted", "url": "http://example.com/file.sourcemap.js"},
  343. {"status": "not_attempted", "url": "http://example.com/file1.js"},
  344. ]
  345. exception = event.interfaces["exception"]
  346. frame_list = exception.values[0].stacktrace.frames
  347. frame = frame_list[0]
  348. assert frame.data["resolved_with"] == "release-old"
  349. assert frame.data["symbolicated"]
  350. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  351. expected = "\treturn a + b; // fôo"
  352. assert frame.context_line == expected
  353. assert frame.post_context == ["}"]
  354. raw_frame_list = exception.values[0].raw_stacktrace.frames
  355. raw_frame = raw_frame_list[0]
  356. assert not raw_frame.pre_context
  357. assert (
  358. raw_frame.context_line
  359. == 'function add(a,b){"use strict";return a+b}function multiply(a,b){"use strict";return a*b}function '
  360. 'divide(a,b){"use strict";try{return multip {snip}'
  361. )
  362. assert raw_frame.post_context == ["//@ sourceMappingURL=file.sourcemap.js"]
  363. assert raw_frame.lineno == 1
  364. # Since we couldn't expand source for the 2nd frame, both
  365. # its raw and original form should be identical, apart from `data.symbolicated`
  366. assert not get_path(frame_list[1], "data", "symbolicated", default=False)
  367. assert raw_frame_list[1].abs_path == frame_list[1].abs_path
  368. assert raw_frame_list[1].filename == frame_list[1].filename
  369. assert raw_frame_list[1].function == frame_list[1].function
  370. assert raw_frame_list[1].in_app == frame_list[1].in_app
  371. assert raw_frame_list[1].lineno == frame_list[1].lineno
  372. assert raw_frame_list[1].colno == frame_list[1].colno
  373. # The second non-js frame should be untouched
  374. assert raw_frame_list[2] == frame_list[2]
  375. @requires_symbolicator
  376. @pytest.mark.symbolicator
  377. def test_sourcemap_webpack(self):
  378. self.project.update_option("sentry:scrape_javascript", False)
  379. release = Release.objects.create(
  380. organization_id=self.project.organization_id, version="abc"
  381. )
  382. release.add_project(self.project)
  383. for file in [
  384. "webpack1.min.js",
  385. "webpack2.min.js",
  386. "webpack1.min.js.map",
  387. "webpack2.min.js.map",
  388. ]:
  389. with open(get_fixture_path(file), "rb") as f:
  390. f1 = File.objects.create(
  391. name=file,
  392. type="release.file",
  393. headers={},
  394. )
  395. f1.putfile(f)
  396. ReleaseFile.objects.create(
  397. name=f"http://example.com/{f1.name}",
  398. release_id=release.id,
  399. organization_id=self.project.organization_id,
  400. file=f1,
  401. )
  402. data = {
  403. "timestamp": self.min_ago,
  404. "message": "hello",
  405. "platform": "javascript",
  406. "release": "abc",
  407. "exception": {
  408. "values": [
  409. {
  410. "type": "Error",
  411. "stacktrace": {
  412. "frames": [
  413. {
  414. "abs_path": "http://example.com/webpack1.min.js",
  415. "filename": "webpack1.min.js",
  416. "lineno": 1,
  417. "colno": 183,
  418. "function": "i",
  419. },
  420. {
  421. "abs_path": "http://example.com/webpack2.min.js",
  422. "filename": "webpack2.min.js",
  423. "lineno": 1,
  424. "colno": 183,
  425. "function": "i",
  426. },
  427. ]
  428. },
  429. }
  430. ]
  431. },
  432. }
  433. event = self.post_and_retrieve_event(data)
  434. exception = event.interfaces["exception"]
  435. frame_list = exception.values[0].stacktrace.frames
  436. assert event.data["scraping_attempts"] == [
  437. {"url": "http://example.com/webpack1.min.js", "status": "not_attempted"},
  438. {"url": "http://example.com/webpack1.min.js.map", "status": "not_attempted"},
  439. {"url": "http://example.com/webpack2.min.js", "status": "not_attempted"},
  440. {"url": "http://example.com/webpack2.min.js.map", "status": "not_attempted"},
  441. ]
  442. # The first frame should be in_app.
  443. first_frame = frame_list[0]
  444. assert first_frame.in_app
  445. assert first_frame.function == "test"
  446. assert first_frame.pre_context == [
  447. " cb(data);",
  448. " }",
  449. "",
  450. " function test() {",
  451. " var data = {failed: true, value: 42};",
  452. ]
  453. assert first_frame.context_line == " invoke(data);"
  454. assert first_frame.post_context == [
  455. " }",
  456. "",
  457. " return test;",
  458. "})();",
  459. ]
  460. # The second frame should be identical to the first, except not in_app.
  461. second_frame = frame_list[1]
  462. assert not second_frame.in_app
  463. assert second_frame.function == first_frame.function
  464. assert second_frame.context_line == first_frame.context_line
  465. assert second_frame.pre_context == first_frame.pre_context
  466. assert second_frame.post_context == first_frame.post_context
  467. @requires_symbolicator
  468. @pytest.mark.symbolicator
  469. def test_sourcemap_embedded_source_expansion(self):
  470. self.project.update_option("sentry:scrape_javascript", False)
  471. release = Release.objects.create(
  472. organization_id=self.project.organization_id, version="abc"
  473. )
  474. release.add_project(self.project)
  475. for file in ["embedded.js", "embedded.js.map"]:
  476. with open(get_fixture_path(file), "rb") as f:
  477. f1 = File.objects.create(
  478. name=file,
  479. type="release.file",
  480. headers={},
  481. )
  482. f1.putfile(f)
  483. ReleaseFile.objects.create(
  484. name=f"http://example.com/{f1.name}",
  485. release_id=release.id,
  486. organization_id=self.project.organization_id,
  487. file=f1,
  488. )
  489. data = {
  490. "timestamp": self.min_ago,
  491. "message": "hello",
  492. "platform": "javascript",
  493. "release": "abc",
  494. "exception": {
  495. "values": [
  496. {
  497. "type": "Error",
  498. "stacktrace": {
  499. "frames": [
  500. {
  501. "abs_path": "http://example.com/embedded.js",
  502. "filename": "file.min.js",
  503. "lineno": 1,
  504. "colno": 39,
  505. },
  506. # NOTE: Intentionally source is not retrieved from this HTML file
  507. {
  508. "function": 'function: "HTMLDocument.<anonymous>"',
  509. "abs_path": "http//example.com/index.html",
  510. "filename": "index.html",
  511. "lineno": 283,
  512. "colno": 17,
  513. "in_app": False,
  514. },
  515. ]
  516. },
  517. }
  518. ]
  519. },
  520. }
  521. event = self.post_and_retrieve_event(data)
  522. assert event.data["errors"] == [
  523. {
  524. "type": "js_no_source",
  525. "symbolicator_type": "missing_source",
  526. "url": "http//example.com/index.html",
  527. }
  528. ]
  529. assert event.data["scraping_attempts"] == [
  530. {"status": "not_attempted", "url": "http://example.com/embedded.js"},
  531. {"status": "not_attempted", "url": "http://example.com/embedded.js.map"},
  532. ]
  533. exception = event.interfaces["exception"]
  534. frame_list = exception.values[0].stacktrace.frames
  535. frame = frame_list[0]
  536. assert frame.data["resolved_with"] == "release-old"
  537. assert frame.data["symbolicated"]
  538. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  539. expected = "\treturn a + b; // fôo"
  540. assert frame.context_line == expected
  541. assert frame.post_context == ["}"]
  542. @requires_symbolicator
  543. @pytest.mark.symbolicator
  544. def test_sourcemap_nofiles_source_expansion(self):
  545. project = self.project
  546. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  547. release.add_project(project)
  548. with open(get_fixture_path("nofiles.js"), "rb") as f:
  549. f_minified = File.objects.create(
  550. name="nofiles.js", type="release.file", headers={"Content-Type": "application/json"}
  551. )
  552. f_minified.putfile(f)
  553. ReleaseFile.objects.create(
  554. name=f"~/{f_minified.name}",
  555. release_id=release.id,
  556. organization_id=project.organization_id,
  557. file=f_minified,
  558. )
  559. with open(get_fixture_path("nofiles.js.map"), "rb") as f:
  560. f_sourcemap = File.objects.create(
  561. name="nofiles.js.map",
  562. type="release.file",
  563. headers={"Content-Type": "application/json"},
  564. )
  565. f_sourcemap.putfile(f)
  566. ReleaseFile.objects.create(
  567. name=f"app:///{f_sourcemap.name}",
  568. release_id=release.id,
  569. organization_id=project.organization_id,
  570. file=f_sourcemap,
  571. )
  572. data = {
  573. "timestamp": self.min_ago,
  574. "message": "hello",
  575. "platform": "javascript",
  576. "release": "abc",
  577. "exception": {
  578. "values": [
  579. {
  580. "type": "Error",
  581. "stacktrace": {
  582. "frames": [{"abs_path": "app:///nofiles.js", "lineno": 1, "colno": 39}]
  583. },
  584. }
  585. ]
  586. },
  587. }
  588. event = self.post_and_retrieve_event(data)
  589. assert "errors" not in event.data
  590. assert event.data["scraping_attempts"] == [
  591. {"url": "app:///nofiles.js", "status": "not_attempted"},
  592. {"url": "app:///nofiles.js.map", "status": "not_attempted"},
  593. ]
  594. exception = event.interfaces["exception"]
  595. frame_list = exception.values[0].stacktrace.frames
  596. assert len(frame_list) == 1
  597. frame = frame_list[0]
  598. assert frame.data["resolved_with"] == "release-old"
  599. assert frame.data["symbolicated"]
  600. assert frame.abs_path == "app:///nofiles.js"
  601. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  602. assert frame.context_line == "\treturn a + b; // fôo"
  603. assert frame.post_context == ["}"]
  604. @requires_symbolicator
  605. @pytest.mark.symbolicator
  606. def test_indexed_sourcemap_source_expansion(self):
  607. self.project.update_option("sentry:scrape_javascript", False)
  608. release = Release.objects.create(
  609. organization_id=self.project.organization_id, version="abc"
  610. )
  611. release.add_project(self.project)
  612. for file in ["indexed.min.js", "file1.js", "file2.js", "indexed.sourcemap.js"]:
  613. with open(get_fixture_path(file), "rb") as f:
  614. f1 = File.objects.create(
  615. name=file,
  616. type="release.file",
  617. headers={},
  618. )
  619. f1.putfile(f)
  620. ReleaseFile.objects.create(
  621. name=f"http://example.com/{f1.name}",
  622. release_id=release.id,
  623. organization_id=self.project.organization_id,
  624. file=f1,
  625. )
  626. data = {
  627. "timestamp": self.min_ago,
  628. "message": "hello",
  629. "platform": "javascript",
  630. "release": "abc",
  631. "exception": {
  632. "values": [
  633. {
  634. "type": "Error",
  635. "stacktrace": {
  636. "frames": [
  637. {
  638. "abs_path": "http://example.com/indexed.min.js",
  639. "filename": "indexed.min.js",
  640. "lineno": 1,
  641. "colno": 39,
  642. },
  643. {
  644. "abs_path": "http://example.com/indexed.min.js",
  645. "filename": "indexed.min.js",
  646. "lineno": 2,
  647. "colno": 44,
  648. },
  649. ]
  650. },
  651. }
  652. ]
  653. },
  654. }
  655. event = self.post_and_retrieve_event(data)
  656. assert "errors" not in event.data
  657. assert event.data["scraping_attempts"] == [
  658. {"status": "not_attempted", "url": "http://example.com/indexed.min.js"},
  659. {"status": "not_attempted", "url": "http://example.com/indexed.sourcemap.js"},
  660. {"status": "not_attempted", "url": "http://example.com/file1.js"},
  661. {"status": "not_attempted", "url": "http://example.com/file2.js"},
  662. ]
  663. exception = event.interfaces["exception"]
  664. frame_list = exception.values[0].stacktrace.frames
  665. frame = frame_list[0]
  666. assert frame.data["resolved_with"] == "release-old"
  667. assert frame.data["symbolicated"]
  668. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  669. expected = "\treturn a + b; // fôo"
  670. assert frame.context_line == expected
  671. assert frame.post_context == ["}"]
  672. raw_frame_list = exception.values[0].raw_stacktrace.frames
  673. raw_frame = raw_frame_list[0]
  674. assert not raw_frame.pre_context
  675. assert raw_frame.context_line == 'function add(a,b){"use strict";return a+b}'
  676. assert raw_frame.post_context == [
  677. 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  678. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}",
  679. "//# sourceMappingURL=indexed.sourcemap.js",
  680. ]
  681. assert raw_frame.lineno == 1
  682. frame = frame_list[1]
  683. assert frame.data["resolved_with"] == "release-old"
  684. assert frame.data["symbolicated"]
  685. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  686. assert frame.context_line == "\treturn a * b;"
  687. assert frame.post_context == [
  688. "}",
  689. "function divide(a, b) {",
  690. '\t"use strict";',
  691. "\ttry {",
  692. "\t\treturn multiply(add(a, b), a, b) / c;",
  693. ]
  694. raw_frame = raw_frame_list[1]
  695. assert raw_frame.pre_context == ['function add(a,b){"use strict";return a+b}']
  696. assert (
  697. raw_frame.context_line
  698. == 'function multiply(a,b){"use strict";return a*b}function divide(a,b){"use strict";try{return multiply('
  699. "add(a,b),a,b)/c}catch(e){Raven.captureE {snip}"
  700. )
  701. assert raw_frame.post_context == ["//# sourceMappingURL=indexed.sourcemap.js"]
  702. assert raw_frame.lineno == 2
  703. @requires_symbolicator
  704. @pytest.mark.symbolicator
  705. def test_expansion_via_debug(self):
  706. project = self.project
  707. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  708. release.add_project(project)
  709. # file.min.js
  710. # ------------
  711. with open(get_fixture_path("file.min.js"), "rb") as f:
  712. f_minified = File.objects.create(
  713. name="file.min.js",
  714. type="release.file",
  715. headers={"Content-Type": "application/json"},
  716. )
  717. f_minified.putfile(f)
  718. # Intentionally omit hostname - use alternate artifact path lookup instead
  719. # /file1.js vs http://example.com/file1.js
  720. ReleaseFile.objects.create(
  721. name=f"~/{f_minified.name}?foo=bar",
  722. release_id=release.id,
  723. organization_id=project.organization_id,
  724. file=f_minified,
  725. )
  726. # file1.js
  727. # ---------
  728. with open(get_fixture_path("file1.js"), "rb") as f:
  729. f1 = File.objects.create(
  730. name="file1.js", type="release.file", headers={"Content-Type": "application/json"}
  731. )
  732. f1.putfile(f)
  733. ReleaseFile.objects.create(
  734. name=f"http://example.com/{f1.name}",
  735. release_id=release.id,
  736. organization_id=project.organization_id,
  737. file=f1,
  738. )
  739. # file2.js
  740. # ----------
  741. with open(get_fixture_path("file2.js"), "rb") as f:
  742. f2 = File.objects.create(
  743. name="file2.js", type="release.file", headers={"Content-Type": "application/json"}
  744. )
  745. f2.putfile(f)
  746. ReleaseFile.objects.create(
  747. name=f"http://example.com/{f2.name}",
  748. release_id=release.id,
  749. organization_id=project.organization_id,
  750. file=f2,
  751. )
  752. # To verify that the full url has priority over the relative url,
  753. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  754. # hostname that points to an empty file. If the processor chooses
  755. # this empty file over the correct file2.js, it will not locate
  756. # context for the 2nd frame.
  757. with open(get_fixture_path("empty.js"), "rb") as f:
  758. f2_empty = File.objects.create(
  759. name="empty.js", type="release.file", headers={"Content-Type": "application/json"}
  760. )
  761. f2_empty.putfile(f)
  762. ReleaseFile.objects.create(
  763. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  764. release_id=release.id,
  765. organization_id=project.organization_id,
  766. file=f2_empty,
  767. )
  768. # sourcemap
  769. # ----------
  770. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  771. f_sourcemap = File.objects.create(
  772. name="file.sourcemap.js",
  773. type="release.file",
  774. headers={"Content-Type": "application/json"},
  775. )
  776. f_sourcemap.putfile(f)
  777. ReleaseFile.objects.create(
  778. name=f"http://example.com/{f_sourcemap.name}",
  779. release_id=release.id,
  780. organization_id=project.organization_id,
  781. file=f_sourcemap,
  782. )
  783. data = {
  784. "timestamp": self.min_ago,
  785. "message": "hello",
  786. "platform": "javascript",
  787. "release": "abc",
  788. "exception": {
  789. "values": [
  790. {
  791. "type": "Error",
  792. "stacktrace": {
  793. "frames": [
  794. {
  795. "abs_path": "http://example.com/file.min.js?foo=bar",
  796. "filename": "file.min.js",
  797. "lineno": 1,
  798. "colno": 39,
  799. },
  800. {
  801. "abs_path": "http://example.com/file.min.js?foo=bar",
  802. "filename": "file.min.js",
  803. "lineno": 1,
  804. "colno": 79,
  805. },
  806. ]
  807. },
  808. }
  809. ]
  810. },
  811. }
  812. event = self.post_and_retrieve_event(data)
  813. assert "errors" not in event.data
  814. exception = event.interfaces["exception"]
  815. frame_list = exception.values[0].stacktrace.frames
  816. frame = frame_list[0]
  817. assert frame.data["resolved_with"] == "release-old"
  818. assert frame.data["symbolicated"]
  819. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  820. assert frame.context_line == "\treturn a + b; // fôo"
  821. assert frame.post_context == ["}"]
  822. frame = frame_list[1]
  823. assert frame.data["resolved_with"] == "release-old"
  824. assert frame.data["symbolicated"]
  825. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  826. assert frame.context_line == "\treturn a * b;"
  827. assert frame.post_context == [
  828. "}",
  829. "function divide(a, b) {",
  830. '\t"use strict";',
  831. "\ttry {",
  832. "\t\treturn multiply(add(a, b), a, b) / c;",
  833. ]
  834. @requires_symbolicator
  835. @pytest.mark.symbolicator
  836. def test_expansion_via_distribution_release_artifacts(self):
  837. project = self.project
  838. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  839. release.add_project(project)
  840. dist = release.add_dist("foo")
  841. # file.min.js
  842. # ------------
  843. with open(get_fixture_path("file.min.js"), "rb") as f:
  844. f_minified = File.objects.create(
  845. name="file.min.js",
  846. type="release.file",
  847. headers={"Content-Type": "application/json"},
  848. )
  849. f_minified.putfile(f)
  850. # Intentionally omit hostname - use alternate artifact path lookup instead
  851. # /file1.js vs http://example.com/file1.js
  852. ReleaseFile.objects.create(
  853. name=f"~/{f_minified.name}?foo=bar",
  854. release_id=release.id,
  855. dist_id=dist.id,
  856. organization_id=project.organization_id,
  857. file=f_minified,
  858. )
  859. # file1.js
  860. # ---------
  861. with open(get_fixture_path("file1.js"), "rb") as f:
  862. f1 = File.objects.create(
  863. name="file1.js",
  864. type="release.file",
  865. headers={"Content-Type": "application/json"},
  866. )
  867. f1.putfile(f)
  868. ReleaseFile.objects.create(
  869. name=f"http://example.com/{f1.name}",
  870. release_id=release.id,
  871. dist_id=dist.id,
  872. organization_id=project.organization_id,
  873. file=f1,
  874. )
  875. # file2.js
  876. # ----------
  877. with open(get_fixture_path("file2.js"), "rb") as f:
  878. f2 = File.objects.create(
  879. name="file2.js",
  880. type="release.file",
  881. headers={"Content-Type": "application/json"},
  882. )
  883. f2.putfile(f)
  884. ReleaseFile.objects.create(
  885. name=f"http://example.com/{f2.name}",
  886. release_id=release.id,
  887. dist_id=dist.id,
  888. organization_id=project.organization_id,
  889. file=f2,
  890. )
  891. # To verify that the full url has priority over the relative url,
  892. # we will also add a second ReleaseFile alias for file2.js (f3) w/o
  893. # hostname that points to an empty file. If the processor chooses
  894. # this empty file over the correct file2.js, it will not locate
  895. # context for the 2nd frame.
  896. with open(get_fixture_path("empty.js"), "rb") as f:
  897. f2_empty = File.objects.create(
  898. name="empty.js",
  899. type="release.file",
  900. headers={"Content-Type": "application/json"},
  901. )
  902. f2_empty.putfile(f)
  903. ReleaseFile.objects.create(
  904. name=f"~/{f2.name}", # intentionally using f2.name ("file2.js")
  905. release_id=release.id,
  906. dist_id=dist.id,
  907. organization_id=project.organization_id,
  908. file=f2_empty,
  909. )
  910. # sourcemap
  911. # ----------
  912. with open(get_fixture_path("file.sourcemap.js"), "rb") as f:
  913. f_sourcemap = File.objects.create(
  914. name="file.sourcemap.js",
  915. type="release.file",
  916. headers={"Content-Type": "application/json"},
  917. )
  918. f_sourcemap.putfile(f)
  919. ReleaseFile.objects.create(
  920. name=f"http://example.com/{f_sourcemap.name}",
  921. release_id=release.id,
  922. dist_id=dist.id,
  923. organization_id=project.organization_id,
  924. file=f_sourcemap,
  925. )
  926. data = {
  927. "timestamp": self.min_ago,
  928. "message": "hello",
  929. "platform": "javascript",
  930. "release": "abc",
  931. "dist": "foo",
  932. "exception": {
  933. "values": [
  934. {
  935. "type": "Error",
  936. "stacktrace": {
  937. "frames": [
  938. {
  939. "abs_path": "http://example.com/file.min.js?foo=bar",
  940. "filename": "file.min.js",
  941. "lineno": 1,
  942. "colno": 39,
  943. },
  944. {
  945. "abs_path": "http://example.com/file.min.js?foo=bar",
  946. "filename": "file.min.js",
  947. "lineno": 1,
  948. "colno": 79,
  949. },
  950. ]
  951. },
  952. }
  953. ]
  954. },
  955. }
  956. event = self.post_and_retrieve_event(data)
  957. assert "errors" not in event.data
  958. exception = event.interfaces["exception"]
  959. frame_list = exception.values[0].stacktrace.frames
  960. frame = frame_list[0]
  961. assert frame.data["resolved_with"] == "release-old"
  962. assert frame.data["symbolicated"]
  963. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  964. assert frame.context_line == "\treturn a + b; // fôo"
  965. assert frame.post_context == ["}"]
  966. frame = frame_list[1]
  967. assert frame.data["resolved_with"] == "release-old"
  968. assert frame.data["symbolicated"]
  969. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  970. assert frame.context_line == "\treturn a * b;"
  971. assert frame.post_context == [
  972. "}",
  973. "function divide(a, b) {",
  974. '\t"use strict";',
  975. "\ttry {",
  976. "\t\treturn multiply(add(a, b), a, b) / c;",
  977. ]
  978. def _test_expansion_via_release_archive(self, link_sourcemaps: bool):
  979. project = self.project
  980. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  981. release.add_project(project)
  982. manifest = {
  983. "org": self.organization.slug,
  984. "release": release.version,
  985. "files": {
  986. "files/_/_/file.min.js": {
  987. "url": "http://example.com/file.min.js",
  988. "type": "minified_source",
  989. },
  990. "files/_/_/file1.js": {
  991. "url": "http://example.com/file1.js",
  992. "type": "source",
  993. },
  994. "files/_/_/file2.js": {
  995. "url": "http://example.com/file2.js",
  996. "type": "source",
  997. },
  998. "files/_/_/file.sourcemap.js": {
  999. "url": "http://example.com/file.sourcemap.js",
  1000. "type": "source_map",
  1001. },
  1002. },
  1003. }
  1004. compressed = BytesIO(b"SYSB")
  1005. with zipfile.ZipFile(compressed, "a") as zip_file:
  1006. for rel_path, entry in manifest["files"].items():
  1007. name = os.path.basename(rel_path)
  1008. content = load_fixture(name)
  1009. if name == "file.min.js" and not link_sourcemaps:
  1010. # Remove link to source map, add to header instead
  1011. content = content.replace(b"//@ sourceMappingURL=file.sourcemap.js", b"")
  1012. entry["headers"] = {"Sourcemap": "file.sourcemap.js"}
  1013. zip_file.writestr(rel_path, content)
  1014. zip_file.writestr("manifest.json", json.dumps(manifest))
  1015. compressed.seek(0)
  1016. file = File.objects.create(name="doesnt_matter", type="release.bundle")
  1017. file.putfile(compressed)
  1018. update_artifact_index(release, None, file)
  1019. data = {
  1020. "timestamp": self.min_ago,
  1021. "message": "hello",
  1022. "platform": "javascript",
  1023. "release": "abc",
  1024. "exception": {
  1025. "values": [
  1026. {
  1027. "type": "Error",
  1028. "stacktrace": {
  1029. "frames": [
  1030. {
  1031. "abs_path": "http://example.com/file.min.js",
  1032. "filename": "file.min.js",
  1033. "lineno": 1,
  1034. "colno": 39,
  1035. },
  1036. {
  1037. "abs_path": "http://example.com/file.min.js",
  1038. "filename": "file.min.js",
  1039. "lineno": 1,
  1040. "colno": 79,
  1041. },
  1042. ]
  1043. },
  1044. }
  1045. ]
  1046. },
  1047. }
  1048. event = self.post_and_retrieve_event(data)
  1049. assert "errors" not in event.data
  1050. exception = event.interfaces["exception"]
  1051. frame_list = exception.values[0].stacktrace.frames
  1052. frame = frame_list[0]
  1053. assert frame.data["resolved_with"] == "release-old"
  1054. assert frame.data["symbolicated"]
  1055. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1056. assert frame.context_line == "\treturn a + b; // fôo"
  1057. assert frame.post_context == ["}"]
  1058. frame = frame_list[1]
  1059. assert frame.data["resolved_with"] == "release-old"
  1060. assert frame.data["symbolicated"]
  1061. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1062. assert frame.context_line == "\treturn a * b;"
  1063. assert frame.post_context == [
  1064. "}",
  1065. "function divide(a, b) {",
  1066. '\t"use strict";',
  1067. "\ttry {",
  1068. "\t\treturn multiply(add(a, b), a, b) / c;",
  1069. ]
  1070. @requires_symbolicator
  1071. @pytest.mark.symbolicator
  1072. def test_expansion_via_release_archive(self):
  1073. self._test_expansion_via_release_archive(link_sourcemaps=True)
  1074. @requires_symbolicator
  1075. @pytest.mark.symbolicator
  1076. def test_expansion_via_release_archive_no_sourcemap_link(self):
  1077. self._test_expansion_via_release_archive(link_sourcemaps=False)
  1078. @requires_symbolicator
  1079. @pytest.mark.symbolicator
  1080. def test_node_processing(self):
  1081. project = self.project
  1082. release = Release.objects.create(
  1083. organization_id=project.organization_id, version="nodeabc123"
  1084. )
  1085. release.add_project(project)
  1086. with open(get_fixture_path("dist.bundle.js"), "rb") as f:
  1087. f_minified = File.objects.create(
  1088. name="dist.bundle.js",
  1089. type="release.file",
  1090. headers={"Content-Type": "application/javascript"},
  1091. )
  1092. f_minified.putfile(f)
  1093. ReleaseFile.objects.create(
  1094. name=f"~/{f_minified.name}",
  1095. release_id=release.id,
  1096. organization_id=project.organization_id,
  1097. file=f_minified,
  1098. )
  1099. with open(get_fixture_path("dist.bundle.js.map"), "rb") as f:
  1100. f_sourcemap = File.objects.create(
  1101. name="dist.bundle.js.map",
  1102. type="release.file",
  1103. headers={"Content-Type": "application/javascript"},
  1104. )
  1105. f_sourcemap.putfile(f)
  1106. ReleaseFile.objects.create(
  1107. name=f"~/{f_sourcemap.name}",
  1108. release_id=release.id,
  1109. organization_id=project.organization_id,
  1110. file=f_sourcemap,
  1111. )
  1112. data = {
  1113. "timestamp": self.min_ago,
  1114. "message": "hello",
  1115. "platform": "node",
  1116. "release": "nodeabc123",
  1117. "exception": {
  1118. "values": [
  1119. {
  1120. "type": "Error",
  1121. "stacktrace": {
  1122. "frames": [
  1123. {
  1124. "filename": "app:///dist.bundle.js",
  1125. "function": "bar",
  1126. "lineno": 9,
  1127. "colno": 2321,
  1128. },
  1129. {
  1130. "filename": "app:///dist.bundle.js",
  1131. "function": "foo",
  1132. "lineno": 3,
  1133. "colno": 2308,
  1134. },
  1135. {
  1136. "filename": "app:///dist.bundle.js",
  1137. "function": "App",
  1138. "lineno": 3,
  1139. "colno": 1011,
  1140. },
  1141. {
  1142. "filename": "app:///dist.bundle.js",
  1143. "function": "Object.<anonymous>",
  1144. "lineno": 1,
  1145. "colno": 1014,
  1146. },
  1147. {
  1148. "filename": "app:///dist.bundle.js",
  1149. "function": "__webpack_require__",
  1150. "lineno": 20,
  1151. "colno": 30,
  1152. },
  1153. {
  1154. "filename": "app:///dist.bundle.js",
  1155. "function": "<unknown>",
  1156. "lineno": 18,
  1157. "colno": 63,
  1158. },
  1159. ]
  1160. },
  1161. }
  1162. ]
  1163. },
  1164. }
  1165. event = self.post_and_retrieve_event(data)
  1166. exception = event.interfaces["exception"]
  1167. frame_list = exception.values[0].stacktrace.frames
  1168. assert len(frame_list) == 6
  1169. def assert_abs_path(abs_path):
  1170. # This makes the test assertion forward compatible with percent-encoded URLs
  1171. # See https://github.com/getsentry/symbolicator/pull/1137
  1172. assert abs_path in (
  1173. "webpack:///webpack/bootstrap d9a5a31d9276b73873d3",
  1174. "webpack:///webpack/bootstrap%20d9a5a31d9276b73873d3",
  1175. )
  1176. assert_abs_path(frame_list[0].abs_path)
  1177. assert frame_list[0].function == "bar"
  1178. assert frame_list[0].lineno == 8
  1179. assert_abs_path(frame_list[1].abs_path)
  1180. assert frame_list[1].function == "foo"
  1181. assert frame_list[1].lineno == 2
  1182. assert_abs_path(frame_list[2].abs_path)
  1183. assert frame_list[2].function == "App"
  1184. assert frame_list[2].lineno == 2
  1185. assert_abs_path(frame_list[3].abs_path)
  1186. assert frame_list[3].function == "Object.<anonymous>"
  1187. assert frame_list[3].lineno == 1
  1188. assert_abs_path(frame_list[4].abs_path)
  1189. assert frame_list[4].function == "__webpack_require__"
  1190. assert frame_list[4].lineno == 19
  1191. assert_abs_path(frame_list[5].abs_path)
  1192. assert frame_list[5].function == "<unknown>"
  1193. assert frame_list[5].lineno == 16
  1194. @responses.activate
  1195. def test_no_fetch_from_http(self):
  1196. responses.add(
  1197. responses.GET,
  1198. "http://example.com/node_app.min.js",
  1199. body=load_fixture("node_app.min.js"),
  1200. content_type="application/javascript; charset=utf-8",
  1201. )
  1202. responses.add(
  1203. responses.GET,
  1204. "http://example.com/node_app.min.js.map",
  1205. body=load_fixture("node_app.min.js.map"),
  1206. content_type="application/javascript; charset=utf-8",
  1207. )
  1208. responses.add_passthru(
  1209. settings.SENTRY_SNUBA + "/tests/entities/generic_metrics_counters/insert",
  1210. )
  1211. data = {
  1212. "timestamp": self.min_ago,
  1213. "message": "hello",
  1214. "platform": "node",
  1215. "exception": {
  1216. "values": [
  1217. {
  1218. "type": "Error",
  1219. "stacktrace": {
  1220. "frames": [
  1221. {
  1222. "abs_path": "node_bootstrap.js",
  1223. "filename": "node_bootstrap.js",
  1224. "lineno": 1,
  1225. "colno": 38,
  1226. },
  1227. {
  1228. "abs_path": "timers.js",
  1229. "filename": "timers.js",
  1230. "lineno": 1,
  1231. "colno": 39,
  1232. },
  1233. {
  1234. "abs_path": "webpack:///internal",
  1235. "filename": "internal",
  1236. "lineno": 1,
  1237. "colno": 43,
  1238. },
  1239. {
  1240. "abs_path": "webpack:///~/some_dep/file.js",
  1241. "filename": "file.js",
  1242. "lineno": 1,
  1243. "colno": 41,
  1244. },
  1245. {
  1246. "abs_path": "webpack:///./node_modules/file.js",
  1247. "filename": "file.js",
  1248. "lineno": 1,
  1249. "colno": 42,
  1250. },
  1251. {
  1252. "abs_path": "http://example.com/node_app.min.js",
  1253. "filename": "node_app.min.js",
  1254. "lineno": 1,
  1255. "colno": 40,
  1256. },
  1257. ]
  1258. },
  1259. }
  1260. ]
  1261. },
  1262. }
  1263. event = self.post_and_retrieve_event(data)
  1264. exception = event.interfaces["exception"]
  1265. frame_list = exception.values[0].stacktrace.frames
  1266. # This one should not process, so this one should be none.
  1267. assert exception.values[0].raw_stacktrace is None
  1268. # None of the in app should update
  1269. for x in range(6):
  1270. assert not frame_list[x].in_app
  1271. @responses.activate
  1272. def test_html_file_with_query_param_ending_with_js_extension(self):
  1273. responses.add(
  1274. responses.GET,
  1275. "http://example.com/file.html",
  1276. body=(
  1277. "<!doctype html><html><head></head><body><script>/*legit case*/</script></body></html>"
  1278. ),
  1279. )
  1280. responses.add_passthru(
  1281. settings.SENTRY_SNUBA + "/tests/entities/generic_metrics_counters/insert",
  1282. )
  1283. data = {
  1284. "timestamp": self.min_ago,
  1285. "message": "hello",
  1286. "platform": "javascript",
  1287. "exception": {
  1288. "values": [
  1289. {
  1290. "type": "Error",
  1291. "stacktrace": {
  1292. "frames": [
  1293. {
  1294. "abs_path": "http://example.com/file.html?sw=iddqd1337.js",
  1295. "filename": "file.html",
  1296. "lineno": 1,
  1297. "colno": 1,
  1298. },
  1299. ]
  1300. },
  1301. }
  1302. ]
  1303. },
  1304. }
  1305. event = self.post_and_retrieve_event(data)
  1306. assert "errors" not in event.data
  1307. @requires_symbolicator
  1308. @pytest.mark.symbolicator
  1309. def test_expansion_with_debug_id(self):
  1310. project = self.project
  1311. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1312. release.add_project(project)
  1313. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1314. compressed = BytesIO(b"SYSB")
  1315. with zipfile.ZipFile(compressed, "a") as zip_file:
  1316. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1317. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1318. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1319. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1320. zip_file.writestr(
  1321. "files/_/_/file.wc.sourcemap.js", load_fixture("file.wc.sourcemap.js")
  1322. )
  1323. zip_file.writestr(
  1324. "manifest.json",
  1325. json.dumps(
  1326. {
  1327. "org": self.organization.slug,
  1328. "release": release.version,
  1329. "files": {
  1330. "files/_/_/file.min.js": {
  1331. "url": "~/file.min.js",
  1332. "type": "minified_source",
  1333. "headers": {
  1334. "content-type": "application/json",
  1335. "debug-id": debug_id,
  1336. "sourcemap": "file.sourcemap.js",
  1337. },
  1338. },
  1339. "files/_/_/file1.js": {
  1340. "url": "~/file1.js",
  1341. "type": "source",
  1342. "headers": {
  1343. "content-type": "application/json",
  1344. },
  1345. },
  1346. "files/_/_/file2.js": {
  1347. "url": "~/file2.js",
  1348. "type": "source",
  1349. "headers": {
  1350. "content-type": "application/json",
  1351. },
  1352. },
  1353. "files/_/_/empty.js": {
  1354. "url": "~/empty.js",
  1355. "type": "source",
  1356. "headers": {
  1357. "content-type": "application/json",
  1358. },
  1359. },
  1360. "files/_/_/file.wc.sourcemap.js": {
  1361. "url": "~/file.wc.sourcemap.js",
  1362. "type": "source_map",
  1363. "headers": {
  1364. "content-type": "application/json",
  1365. "debug-id": debug_id,
  1366. },
  1367. },
  1368. },
  1369. }
  1370. ),
  1371. )
  1372. compressed.seek(0)
  1373. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1374. file.putfile(compressed)
  1375. # We want to also store the release files for this bundle, to check if they work together.
  1376. compressed.seek(0)
  1377. file_for_release = File.objects.create(name="bundle.zip", type="release.bundle")
  1378. file_for_release.putfile(compressed)
  1379. update_artifact_index(release, None, file_for_release)
  1380. artifact_bundle = ArtifactBundle.objects.create(
  1381. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1382. )
  1383. ProjectArtifactBundle.objects.create(
  1384. organization_id=self.organization.id,
  1385. project_id=self.project.id,
  1386. artifact_bundle=artifact_bundle,
  1387. )
  1388. DebugIdArtifactBundle.objects.create(
  1389. organization_id=self.organization.id,
  1390. debug_id=debug_id,
  1391. artifact_bundle=artifact_bundle,
  1392. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1393. )
  1394. DebugIdArtifactBundle.objects.create(
  1395. organization_id=self.organization.id,
  1396. debug_id=debug_id,
  1397. artifact_bundle=artifact_bundle,
  1398. source_file_type=SourceFileType.SOURCE_MAP.value,
  1399. )
  1400. data = {
  1401. "timestamp": self.min_ago,
  1402. "message": "hello",
  1403. "platform": "javascript",
  1404. "release": "abc",
  1405. "exception": {
  1406. "values": [
  1407. {
  1408. "type": "Error",
  1409. "stacktrace": {
  1410. "frames": [
  1411. {
  1412. "abs_path": "http://example.com/file.min.js",
  1413. "filename": "file.min.js",
  1414. "lineno": 1,
  1415. "colno": 39,
  1416. },
  1417. {
  1418. "abs_path": "http://example.com/file.min.js",
  1419. "filename": "file.min.js",
  1420. "lineno": 1,
  1421. "colno": 79,
  1422. },
  1423. # We want also to test the source without minification.
  1424. {
  1425. "abs_path": "http://example.com/file1.js",
  1426. "filename": "file1.js",
  1427. "lineno": 3,
  1428. "colno": 12,
  1429. },
  1430. ]
  1431. },
  1432. }
  1433. ]
  1434. },
  1435. "debug_meta": {
  1436. "images": [
  1437. {
  1438. "type": "sourcemap",
  1439. "debug_id": debug_id,
  1440. "code_file": "http://example.com/file.min.js",
  1441. }
  1442. ]
  1443. },
  1444. }
  1445. event = self.post_and_retrieve_event(data)
  1446. assert "errors" not in event.data
  1447. exception = event.interfaces["exception"]
  1448. frame_list = exception.values[0].stacktrace.frames
  1449. frame = frame_list[0]
  1450. assert frame.data["resolved_with"] == "debug-id"
  1451. assert frame.data["symbolicated"]
  1452. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1453. assert frame.context_line == "\treturn a + b; // fôo"
  1454. assert frame.post_context == ["}"]
  1455. frame = frame_list[1]
  1456. assert frame.data["resolved_with"] == "debug-id"
  1457. assert frame.data["symbolicated"]
  1458. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1459. assert frame.context_line == "\treturn a * b;"
  1460. assert frame.post_context == [
  1461. "}",
  1462. "function divide(a, b) {",
  1463. '\t"use strict";',
  1464. "\ttry {",
  1465. "\t\treturn multiply(add(a, b), a, b) / c;",
  1466. ]
  1467. frame = frame_list[2]
  1468. assert "resolved_with" not in frame.data
  1469. assert not frame.data.get("symbolicated", False)
  1470. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1471. assert frame.context_line == "\treturn a + b; // fôo"
  1472. assert frame.post_context == ["}"]
  1473. @requires_symbolicator
  1474. @pytest.mark.symbolicator
  1475. def test_expansion_with_debug_id_and_sourcemap_without_sources_content(self):
  1476. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1477. compressed = BytesIO(b"SYSB")
  1478. with zipfile.ZipFile(compressed, "a") as zip_file:
  1479. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1480. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1481. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1482. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1483. zip_file.writestr("files/_/_/file.sourcemap.js", load_fixture("file.sourcemap.js"))
  1484. zip_file.writestr(
  1485. "manifest.json",
  1486. json.dumps(
  1487. {
  1488. "files": {
  1489. "files/_/_/file.min.js": {
  1490. "url": "~/file.min.js",
  1491. "type": "minified_source",
  1492. "headers": {
  1493. "content-type": "application/json",
  1494. "debug-id": debug_id,
  1495. "sourcemap": "file.sourcemap.js",
  1496. },
  1497. },
  1498. "files/_/_/file1.js": {
  1499. "url": "~/file1.js",
  1500. "type": "source",
  1501. "headers": {
  1502. "content-type": "application/json",
  1503. },
  1504. },
  1505. "files/_/_/file2.js": {
  1506. "url": "~/file2.js",
  1507. "type": "source",
  1508. "headers": {
  1509. "content-type": "application/json",
  1510. },
  1511. },
  1512. "files/_/_/empty.js": {
  1513. "url": "~/empty.js",
  1514. "type": "source",
  1515. "headers": {
  1516. "content-type": "application/json",
  1517. },
  1518. },
  1519. "files/_/_/file.sourcemap.js": {
  1520. "url": "~/file.sourcemap.js",
  1521. "type": "source_map",
  1522. "headers": {
  1523. "content-type": "application/json",
  1524. "debug-id": debug_id,
  1525. },
  1526. },
  1527. }
  1528. }
  1529. ),
  1530. )
  1531. compressed.seek(0)
  1532. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1533. file.putfile(compressed)
  1534. artifact_bundle = ArtifactBundle.objects.create(
  1535. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1536. )
  1537. ProjectArtifactBundle.objects.create(
  1538. organization_id=self.organization.id,
  1539. project_id=self.project.id,
  1540. artifact_bundle=artifact_bundle,
  1541. )
  1542. DebugIdArtifactBundle.objects.create(
  1543. organization_id=self.organization.id,
  1544. debug_id=debug_id,
  1545. artifact_bundle=artifact_bundle,
  1546. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1547. )
  1548. DebugIdArtifactBundle.objects.create(
  1549. organization_id=self.organization.id,
  1550. debug_id=debug_id,
  1551. artifact_bundle=artifact_bundle,
  1552. source_file_type=SourceFileType.SOURCE_MAP.value,
  1553. )
  1554. data = {
  1555. "timestamp": self.min_ago,
  1556. "message": "hello",
  1557. "platform": "javascript",
  1558. "release": "abc",
  1559. "exception": {
  1560. "values": [
  1561. {
  1562. "type": "Error",
  1563. "stacktrace": {
  1564. "frames": [
  1565. {
  1566. "abs_path": "http://example.com/file.min.js",
  1567. "filename": "file.min.js",
  1568. "lineno": 1,
  1569. "colno": 39,
  1570. },
  1571. {
  1572. "abs_path": "http://example.com/file.min.js",
  1573. "filename": "file.min.js",
  1574. "lineno": 1,
  1575. "colno": 79,
  1576. },
  1577. ]
  1578. },
  1579. }
  1580. ]
  1581. },
  1582. "debug_meta": {
  1583. "images": [
  1584. {
  1585. "type": "sourcemap",
  1586. "debug_id": debug_id,
  1587. "code_file": "http://example.com/file.min.js",
  1588. }
  1589. ]
  1590. },
  1591. }
  1592. event = self.post_and_retrieve_event(data)
  1593. assert "errors" not in event.data
  1594. exception = event.interfaces["exception"]
  1595. frame_list = exception.values[0].stacktrace.frames
  1596. frame = frame_list[0]
  1597. assert frame.data["resolved_with"] == "debug-id"
  1598. assert frame.data["symbolicated"]
  1599. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1600. assert frame.context_line == "\treturn a + b; // fôo"
  1601. assert frame.post_context == ["}"]
  1602. frame = frame_list[1]
  1603. assert frame.data["resolved_with"] == "debug-id"
  1604. assert frame.data["symbolicated"]
  1605. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1606. assert frame.context_line == "\treturn a * b;"
  1607. assert frame.post_context == [
  1608. "}",
  1609. "function divide(a, b) {",
  1610. '\t"use strict";',
  1611. "\ttry {",
  1612. "\t\treturn multiply(add(a, b), a, b) / c;",
  1613. ]
  1614. @requires_symbolicator
  1615. @pytest.mark.symbolicator
  1616. def test_expansion_with_debug_id_and_malformed_sourcemap(self):
  1617. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1618. compressed = BytesIO(b"SYSB")
  1619. with zipfile.ZipFile(compressed, "a") as zip_file:
  1620. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1621. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1622. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1623. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1624. zip_file.writestr(
  1625. "files/_/_/file.malformed.sourcemap.js", load_fixture("file.malformed.sourcemap.js")
  1626. )
  1627. zip_file.writestr(
  1628. "manifest.json",
  1629. json.dumps(
  1630. {
  1631. "files": {
  1632. "files/_/_/file.min.js": {
  1633. "url": "~/file.min.js",
  1634. "type": "minified_source",
  1635. "headers": {
  1636. "content-type": "application/json",
  1637. "debug-id": debug_id,
  1638. "sourcemap": "file.malformed.sourcemap.js",
  1639. },
  1640. },
  1641. "files/_/_/file1.js": {
  1642. "url": "~/file1.js",
  1643. "type": "source",
  1644. "headers": {
  1645. "content-type": "application/json",
  1646. },
  1647. },
  1648. "files/_/_/file2.js": {
  1649. "url": "~/file2.js",
  1650. "type": "source",
  1651. "headers": {
  1652. "content-type": "application/json",
  1653. },
  1654. },
  1655. "files/_/_/empty.js": {
  1656. "url": "~/empty.js",
  1657. "type": "source",
  1658. "headers": {
  1659. "content-type": "application/json",
  1660. },
  1661. },
  1662. "files/_/_/file.malformed.sourcemap.js": {
  1663. "url": "~/file.malformed.sourcemap.js",
  1664. "type": "source_map",
  1665. "headers": {
  1666. "content-type": "application/json",
  1667. "debug-id": debug_id,
  1668. },
  1669. },
  1670. }
  1671. }
  1672. ),
  1673. )
  1674. compressed.seek(0)
  1675. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1676. file.putfile(compressed)
  1677. artifact_bundle = ArtifactBundle.objects.create(
  1678. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1679. )
  1680. ProjectArtifactBundle.objects.create(
  1681. organization_id=self.organization.id,
  1682. project_id=self.project.id,
  1683. artifact_bundle=artifact_bundle,
  1684. )
  1685. DebugIdArtifactBundle.objects.create(
  1686. organization_id=self.organization.id,
  1687. debug_id=debug_id,
  1688. artifact_bundle=artifact_bundle,
  1689. source_file_type=SourceFileType.MINIFIED_SOURCE.value,
  1690. )
  1691. DebugIdArtifactBundle.objects.create(
  1692. organization_id=self.organization.id,
  1693. debug_id=debug_id,
  1694. artifact_bundle=artifact_bundle,
  1695. source_file_type=SourceFileType.SOURCE_MAP.value,
  1696. )
  1697. data = {
  1698. "timestamp": self.min_ago,
  1699. "message": "hello",
  1700. "platform": "javascript",
  1701. "release": "abc",
  1702. "exception": {
  1703. "values": [
  1704. {
  1705. "type": "Error",
  1706. "stacktrace": {
  1707. "frames": [
  1708. {
  1709. "abs_path": "http://example.com/file.min.js",
  1710. "filename": "file.min.js",
  1711. "lineno": 1,
  1712. "colno": 39,
  1713. },
  1714. {
  1715. "abs_path": "http://example.com/file.min.js",
  1716. "filename": "file.min.js",
  1717. "lineno": 1,
  1718. "colno": 79,
  1719. },
  1720. ]
  1721. },
  1722. }
  1723. ]
  1724. },
  1725. "debug_meta": {
  1726. "images": [
  1727. {
  1728. "type": "sourcemap",
  1729. "debug_id": debug_id,
  1730. "code_file": "http://example.com/file.min.js",
  1731. }
  1732. ]
  1733. },
  1734. }
  1735. event = self.post_and_retrieve_event(data)
  1736. assert len(event.data["errors"]) == 1
  1737. assert event.data["errors"][0] == {
  1738. "type": "js_invalid_source",
  1739. "symbolicator_type": "malformed_sourcemap",
  1740. "url": "http://example.com/file.malformed.sourcemap.js",
  1741. }
  1742. @requires_symbolicator
  1743. @pytest.mark.symbolicator
  1744. def test_expansion_with_debug_id_not_found(self):
  1745. project = self.project
  1746. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1747. release.add_project(project)
  1748. manifest = {
  1749. "org": self.organization.slug,
  1750. "release": release.version,
  1751. "files": {
  1752. "files/_/_/file.min.js": {
  1753. "url": "http://example.com/file.min.js",
  1754. "type": "minified_source",
  1755. },
  1756. "files/_/_/file1.js": {
  1757. "url": "http://example.com/file1.js",
  1758. "type": "source",
  1759. },
  1760. "files/_/_/file2.js": {
  1761. "url": "http://example.com/file2.js",
  1762. "type": "source",
  1763. },
  1764. "files/_/_/file.sourcemap.js": {
  1765. "url": "http://example.com/file.sourcemap.js",
  1766. "type": "source_map",
  1767. },
  1768. },
  1769. }
  1770. compressed = BytesIO(b"SYSB")
  1771. with zipfile.ZipFile(compressed, "a") as zip_file:
  1772. for rel_path, entry in manifest["files"].items():
  1773. name = os.path.basename(rel_path)
  1774. content = load_fixture(name)
  1775. if name == "file.min.js":
  1776. # Remove link to source map, add to header instead
  1777. content = content.replace(b"//@ sourceMappingURL=file.sourcemap.js", b"")
  1778. entry["headers"] = {"Sourcemap": "file.sourcemap.js"}
  1779. zip_file.writestr(rel_path, content)
  1780. zip_file.writestr("manifest.json", json.dumps(manifest))
  1781. compressed.seek(0)
  1782. file = File.objects.create(name="release_bundle.zip", type="release.bundle")
  1783. file.putfile(compressed)
  1784. update_artifact_index(release, None, file)
  1785. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1786. data = {
  1787. "timestamp": self.min_ago,
  1788. "message": "hello",
  1789. "platform": "javascript",
  1790. "release": "abc",
  1791. "exception": {
  1792. "values": [
  1793. {
  1794. "type": "Error",
  1795. "stacktrace": {
  1796. "frames": [
  1797. {
  1798. "abs_path": "http://example.com/file.min.js",
  1799. "filename": "file.min.js",
  1800. "lineno": 1,
  1801. "colno": 39,
  1802. },
  1803. {
  1804. "abs_path": "http://example.com/file.min.js",
  1805. "filename": "file.min.js",
  1806. "lineno": 1,
  1807. "colno": 79,
  1808. },
  1809. # We want also to test the source without minification.
  1810. {
  1811. "abs_path": "http://example.com/file1.js",
  1812. "filename": "file1.js",
  1813. "lineno": 3,
  1814. "colno": 12,
  1815. },
  1816. ]
  1817. },
  1818. }
  1819. ]
  1820. },
  1821. "debug_meta": {
  1822. "images": [
  1823. {
  1824. "type": "sourcemap",
  1825. "debug_id": debug_id,
  1826. "code_file": "http://example.com/file.min.js",
  1827. }
  1828. ]
  1829. },
  1830. }
  1831. event = self.post_and_retrieve_event(data)
  1832. exception = event.interfaces["exception"]
  1833. frame_list = exception.values[0].stacktrace.frames
  1834. frame = frame_list[0]
  1835. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1836. assert frame.context_line == "\treturn a + b; // fôo"
  1837. assert frame.post_context == ["}"]
  1838. frame = frame_list[1]
  1839. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1840. assert frame.context_line == "\treturn a * b;"
  1841. assert frame.post_context == [
  1842. "}",
  1843. "function divide(a, b) {",
  1844. '\t"use strict";',
  1845. "\ttry {",
  1846. "\t\treturn multiply(add(a, b), a, b) / c;",
  1847. ]
  1848. frame = frame_list[2]
  1849. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1850. assert frame.context_line == "\treturn a + b; // fôo"
  1851. assert frame.post_context == ["}"]
  1852. @requires_symbolicator
  1853. @pytest.mark.symbolicator
  1854. def test_expansion_with_release_dist_pair_x(self):
  1855. project = self.project
  1856. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  1857. release.add_project(project)
  1858. dist = release.add_dist("android")
  1859. # We want to also add debug_id information inside the manifest but not in the stack trace to replicate a
  1860. # real edge case that we can incur in.
  1861. debug_id = "c941d872-af1f-4f0c-a7ff-ad3d295fe153"
  1862. compressed = BytesIO(b"SYSB")
  1863. with zipfile.ZipFile(compressed, "a") as zip_file:
  1864. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  1865. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  1866. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  1867. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  1868. zip_file.writestr(
  1869. "files/_/_/file.wc.sourcemap.js", load_fixture("file.wc.sourcemap.js")
  1870. )
  1871. zip_file.writestr(
  1872. "manifest.json",
  1873. json.dumps(
  1874. {
  1875. "files": {
  1876. "files/_/_/file.min.js": {
  1877. "url": "~/file.min.js",
  1878. "type": "minified_source",
  1879. "headers": {
  1880. "content-type": "application/json",
  1881. "sourcemap": "file.wc.sourcemap.js",
  1882. "debug-id": debug_id,
  1883. },
  1884. },
  1885. "files/_/_/file1.js": {
  1886. "url": "~/file1.js",
  1887. "type": "source",
  1888. "headers": {
  1889. "content-type": "application/json",
  1890. },
  1891. },
  1892. "files/_/_/file2.js": {
  1893. "url": "~/file2.js",
  1894. "type": "source",
  1895. "headers": {
  1896. "content-type": "application/json",
  1897. },
  1898. },
  1899. "files/_/_/empty.js": {
  1900. "url": "~/empty.js",
  1901. "type": "source",
  1902. "headers": {
  1903. "content-type": "application/json",
  1904. },
  1905. },
  1906. "files/_/_/file.wc.sourcemap.js": {
  1907. "url": "~/file.wc.sourcemap.js",
  1908. "type": "source_map",
  1909. "headers": {
  1910. "content-type": "application/json",
  1911. "debug-id": debug_id,
  1912. },
  1913. },
  1914. },
  1915. }
  1916. ),
  1917. )
  1918. compressed.seek(0)
  1919. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  1920. file.putfile(compressed)
  1921. artifact_bundle = ArtifactBundle.objects.create(
  1922. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  1923. )
  1924. ProjectArtifactBundle.objects.create(
  1925. organization_id=self.organization.id,
  1926. project_id=self.project.id,
  1927. artifact_bundle=artifact_bundle,
  1928. )
  1929. ReleaseArtifactBundle.objects.create(
  1930. organization_id=self.organization.id,
  1931. release_name=release.version,
  1932. dist_name=dist.name,
  1933. artifact_bundle=artifact_bundle,
  1934. )
  1935. data = {
  1936. "timestamp": self.min_ago,
  1937. "message": "hello",
  1938. "platform": "javascript",
  1939. "release": release.version,
  1940. "dist": dist.name,
  1941. "exception": {
  1942. "values": [
  1943. {
  1944. "type": "Error",
  1945. "stacktrace": {
  1946. "frames": [
  1947. {
  1948. "abs_path": "http://example.com/file.min.js",
  1949. "filename": "file.min.js",
  1950. "lineno": 1,
  1951. "colno": 39,
  1952. },
  1953. {
  1954. "abs_path": "http://example.com/file.min.js",
  1955. "filename": "file.min.js",
  1956. "lineno": 1,
  1957. "colno": 79,
  1958. },
  1959. # We want also to test the source without minification.
  1960. {
  1961. "abs_path": "http://example.com/file1.js",
  1962. "filename": "file1.js",
  1963. "lineno": 3,
  1964. "colno": 12,
  1965. },
  1966. ]
  1967. },
  1968. }
  1969. ]
  1970. },
  1971. }
  1972. event = self.post_and_retrieve_event(data)
  1973. assert "errors" not in event.data
  1974. exception = event.interfaces["exception"]
  1975. frame_list = exception.values[0].stacktrace.frames
  1976. frame = frame_list[0]
  1977. assert frame.data["resolved_with"] == "release"
  1978. assert frame.data["symbolicated"]
  1979. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1980. assert frame.context_line == "\treturn a + b; // fôo"
  1981. assert frame.post_context == ["}"]
  1982. frame = frame_list[1]
  1983. assert frame.data["resolved_with"] == "release"
  1984. assert frame.data["symbolicated"]
  1985. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  1986. assert frame.context_line == "\treturn a * b;"
  1987. assert frame.post_context == [
  1988. "}",
  1989. "function divide(a, b) {",
  1990. '\t"use strict";',
  1991. "\ttry {",
  1992. "\t\treturn multiply(add(a, b), a, b) / c;",
  1993. ]
  1994. frame = frame_list[2]
  1995. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  1996. assert frame.context_line == "\treturn a + b; // fôo"
  1997. assert frame.post_context == ["}"]
  1998. @requires_symbolicator
  1999. @pytest.mark.symbolicator
  2000. def test_expansion_with_release_dist_pair_and_sourcemap_without_sources_content(self):
  2001. project = self.project
  2002. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  2003. release.add_project(project)
  2004. dist = release.add_dist("android")
  2005. compressed = BytesIO(b"SYSB")
  2006. with zipfile.ZipFile(compressed, "a") as zip_file:
  2007. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  2008. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  2009. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  2010. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  2011. zip_file.writestr("files/_/_/file.sourcemap.js", load_fixture("file.sourcemap.js"))
  2012. zip_file.writestr(
  2013. "manifest.json",
  2014. json.dumps(
  2015. {
  2016. "files": {
  2017. "files/_/_/file.min.js": {
  2018. "url": "~/file.min.js",
  2019. "type": "minified_source",
  2020. "headers": {
  2021. "content-type": "application/json",
  2022. "sourcemap": "file.sourcemap.js",
  2023. },
  2024. },
  2025. "files/_/_/file1.js": {
  2026. "url": "~/file1.js",
  2027. "type": "source",
  2028. "headers": {
  2029. "content-type": "application/json",
  2030. },
  2031. },
  2032. "files/_/_/file2.js": {
  2033. "url": "~/file2.js",
  2034. "type": "source",
  2035. "headers": {
  2036. "content-type": "application/json",
  2037. },
  2038. },
  2039. "files/_/_/empty.js": {
  2040. "url": "~/empty.js",
  2041. "type": "source",
  2042. "headers": {
  2043. "content-type": "application/json",
  2044. },
  2045. },
  2046. "files/_/_/file.sourcemap.js": {
  2047. "url": "~/file.sourcemap.js",
  2048. "type": "source_map",
  2049. "headers": {
  2050. "content-type": "application/json",
  2051. },
  2052. },
  2053. }
  2054. }
  2055. ),
  2056. )
  2057. compressed.seek(0)
  2058. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  2059. file.putfile(compressed)
  2060. artifact_bundle = ArtifactBundle.objects.create(
  2061. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  2062. )
  2063. ProjectArtifactBundle.objects.create(
  2064. organization_id=self.organization.id,
  2065. project_id=self.project.id,
  2066. artifact_bundle=artifact_bundle,
  2067. )
  2068. ReleaseArtifactBundle.objects.create(
  2069. organization_id=self.organization.id,
  2070. release_name=release.version,
  2071. dist_name=dist.name,
  2072. artifact_bundle=artifact_bundle,
  2073. )
  2074. data = {
  2075. "timestamp": self.min_ago,
  2076. "message": "hello",
  2077. "platform": "javascript",
  2078. "release": release.version,
  2079. "dist": dist.name,
  2080. "exception": {
  2081. "values": [
  2082. {
  2083. "type": "Error",
  2084. "stacktrace": {
  2085. "frames": [
  2086. {
  2087. "abs_path": "http://example.com/file.min.js",
  2088. "filename": "file.min.js",
  2089. "lineno": 1,
  2090. "colno": 39,
  2091. },
  2092. {
  2093. "abs_path": "http://example.com/file.min.js",
  2094. "filename": "file.min.js",
  2095. "lineno": 1,
  2096. "colno": 79,
  2097. },
  2098. ]
  2099. },
  2100. }
  2101. ]
  2102. },
  2103. }
  2104. event = self.post_and_retrieve_event(data)
  2105. assert "errors" not in event.data
  2106. exception = event.interfaces["exception"]
  2107. frame_list = exception.values[0].stacktrace.frames
  2108. frame = frame_list[0]
  2109. assert frame.data["resolved_with"] == "release"
  2110. assert frame.data["symbolicated"]
  2111. assert frame.pre_context == ["function add(a, b) {", '\t"use strict";']
  2112. assert frame.context_line == "\treturn a + b; // fôo"
  2113. assert frame.post_context == ["}"]
  2114. frame = frame_list[1]
  2115. assert frame.data["resolved_with"] == "release"
  2116. assert frame.data["symbolicated"]
  2117. assert frame.pre_context == ["function multiply(a, b) {", '\t"use strict";']
  2118. assert frame.context_line == "\treturn a * b;"
  2119. assert frame.post_context == [
  2120. "}",
  2121. "function divide(a, b) {",
  2122. '\t"use strict";',
  2123. "\ttry {",
  2124. "\t\treturn multiply(add(a, b), a, b) / c;",
  2125. ]
  2126. @requires_symbolicator
  2127. @pytest.mark.symbolicator
  2128. def test_expansion_with_release_and_malformed_sourcemap(self):
  2129. project = self.project
  2130. release = Release.objects.create(organization_id=project.organization_id, version="abc")
  2131. release.add_project(project)
  2132. compressed = BytesIO(b"SYSB")
  2133. with zipfile.ZipFile(compressed, "a") as zip_file:
  2134. zip_file.writestr("files/_/_/file.min.js", load_fixture("file.min.js"))
  2135. zip_file.writestr("files/_/_/file1.js", load_fixture("file1.js"))
  2136. zip_file.writestr("files/_/_/file2.js", load_fixture("file2.js"))
  2137. zip_file.writestr("files/_/_/empty.js", load_fixture("empty.js"))
  2138. zip_file.writestr(
  2139. "files/_/_/file.malformed.sourcemap.js", load_fixture("file.malformed.sourcemap.js")
  2140. )
  2141. zip_file.writestr(
  2142. "manifest.json",
  2143. json.dumps(
  2144. {
  2145. "files": {
  2146. "files/_/_/file.min.js": {
  2147. "url": "~/file.min.js",
  2148. "type": "minified_source",
  2149. "headers": {
  2150. "content-type": "application/json",
  2151. "sourcemap": "file.malformed.sourcemap.js",
  2152. },
  2153. },
  2154. "files/_/_/file1.js": {
  2155. "url": "~/file1.js",
  2156. "type": "source",
  2157. "headers": {
  2158. "content-type": "application/json",
  2159. },
  2160. },
  2161. "files/_/_/file2.js": {
  2162. "url": "~/file2.js",
  2163. "type": "source",
  2164. "headers": {
  2165. "content-type": "application/json",
  2166. },
  2167. },
  2168. "files/_/_/empty.js": {
  2169. "url": "~/empty.js",
  2170. "type": "source",
  2171. "headers": {
  2172. "content-type": "application/json",
  2173. },
  2174. },
  2175. "files/_/_/file.malformed.sourcemap.js": {
  2176. "url": "~/file.malformed.sourcemap.js",
  2177. "type": "source_map",
  2178. "headers": {
  2179. "content-type": "application/json",
  2180. },
  2181. },
  2182. }
  2183. }
  2184. ),
  2185. )
  2186. compressed.seek(0)
  2187. file = File.objects.create(name="bundle.zip", type="artifact.bundle")
  2188. file.putfile(compressed)
  2189. artifact_bundle = ArtifactBundle.objects.create(
  2190. organization_id=self.organization.id, bundle_id=uuid4(), file=file, artifact_count=5
  2191. )
  2192. ProjectArtifactBundle.objects.create(
  2193. organization_id=self.organization.id,
  2194. project_id=self.project.id,
  2195. artifact_bundle=artifact_bundle,
  2196. )
  2197. ReleaseArtifactBundle.objects.create(
  2198. organization_id=self.organization.id,
  2199. release_name=release.version,
  2200. artifact_bundle=artifact_bundle,
  2201. )
  2202. data = {
  2203. "timestamp": self.min_ago,
  2204. "message": "hello",
  2205. "platform": "javascript",
  2206. "release": release.version,
  2207. "exception": {
  2208. "values": [
  2209. {
  2210. "type": "Error",
  2211. "stacktrace": {
  2212. "frames": [
  2213. {
  2214. "abs_path": "http://example.com/file.min.js",
  2215. "filename": "file.min.js",
  2216. "lineno": 1,
  2217. "colno": 39,
  2218. },
  2219. {
  2220. "abs_path": "http://example.com/file.min.js",
  2221. "filename": "file.min.js",
  2222. "lineno": 1,
  2223. "colno": 79,
  2224. },
  2225. ]
  2226. },
  2227. }
  2228. ]
  2229. },
  2230. }
  2231. event = self.post_and_retrieve_event(data)
  2232. assert len(event.data["errors"]) == 1
  2233. assert event.data["errors"][0] == {
  2234. "type": "js_invalid_source",
  2235. "symbolicator_type": "malformed_sourcemap",
  2236. "url": "http://example.com/file.malformed.sourcemap.js",
  2237. }