test_plugin.py 97 KB

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