test_plugin.py 115 KB

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