test_plugin.py 112 KB

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