test_event_manager.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, print_function
  3. import logging
  4. import pytest
  5. from datetime import timedelta
  6. from django.conf import settings
  7. from django.utils import timezone
  8. from mock import patch
  9. from time import time
  10. from sentry.app import tsdb
  11. from sentry.constants import MAX_CULPRIT_LENGTH, DEFAULT_LOGGER_NAME
  12. from sentry.event_manager import (
  13. EventManager, EventUser, get_hashes_for_event, get_hashes_from_fingerprint,
  14. generate_culprit, md5_from_hash
  15. )
  16. from sentry.models import (
  17. Activity, Event, Group, GroupRelease, GroupResolution, GroupStatus,
  18. EventMapping, Release
  19. )
  20. from sentry.testutils import TestCase, TransactionTestCase
  21. class EventManagerTest(TransactionTestCase):
  22. def make_event(self, **kwargs):
  23. result = {
  24. 'event_id': 'a' * 32,
  25. 'message': 'foo',
  26. 'timestamp': 1403007314.570599,
  27. 'level': logging.ERROR,
  28. 'logger': 'default',
  29. 'tags': [],
  30. }
  31. result.update(kwargs)
  32. return result
  33. def test_similar_message_prefix_doesnt_group(self):
  34. # we had a regression which caused the default hash to just be
  35. # 'event.message' instead of '[event.message]' which caused it to
  36. # generate a hash per letter
  37. manager = EventManager(self.make_event(message='foo bar'))
  38. manager.normalize()
  39. event1 = manager.save(1)
  40. manager = EventManager(self.make_event(message='foo baz'))
  41. manager.normalize()
  42. event2 = manager.save(1)
  43. assert event1.group_id != event2.group_id
  44. @patch('sentry.signals.regression_signal.send')
  45. def test_broken_regression_signal(self, send):
  46. send.side_effect = Exception()
  47. manager = EventManager(self.make_event())
  48. event = manager.save(1)
  49. assert event.message == 'foo'
  50. assert event.project_id == 1
  51. @patch('sentry.event_manager.should_sample')
  52. def test_saves_event_mapping_when_sampled(self, should_sample):
  53. should_sample.return_value = True
  54. event_id = 'a' * 32
  55. manager = EventManager(self.make_event())
  56. event = manager.save(1)
  57. assert EventMapping.objects.filter(
  58. group_id=event.group_id,
  59. event_id=event_id,
  60. ).exists()
  61. @patch('sentry.event_manager.should_sample')
  62. def test_sample_feature_flag(self, should_sample):
  63. should_sample.return_value = True
  64. manager = EventManager(self.make_event())
  65. with self.feature('projects:sample-events'):
  66. event = manager.save(1)
  67. assert event.id
  68. manager = EventManager(self.make_event())
  69. with self.feature('projects:sample-events', False):
  70. event = manager.save(1)
  71. assert not event.id
  72. def test_tags_as_list(self):
  73. manager = EventManager(self.make_event(tags=[('foo', 'bar')]))
  74. data = manager.normalize()
  75. assert data['tags'] == [('foo', 'bar')]
  76. def test_tags_as_dict(self):
  77. manager = EventManager(self.make_event(tags={'foo': 'bar'}))
  78. data = manager.normalize()
  79. assert data['tags'] == [('foo', 'bar')]
  80. def test_interface_is_relabeled(self):
  81. manager = EventManager(self.make_event(user={'id': '1'}))
  82. data = manager.normalize()
  83. assert data['sentry.interfaces.User'] == {'id': '1'}
  84. assert 'user' not in data
  85. def test_does_default_ip_address_to_user(self):
  86. manager = EventManager(self.make_event(**{
  87. 'sentry.interfaces.Http': {
  88. 'url': 'http://example.com',
  89. 'env': {
  90. 'REMOTE_ADDR': '127.0.0.1',
  91. }
  92. }
  93. }))
  94. data = manager.normalize()
  95. assert data['sentry.interfaces.User']['ip_address'] == '127.0.0.1'
  96. def test_does_default_ip_address_if_present(self):
  97. manager = EventManager(self.make_event(**{
  98. 'sentry.interfaces.Http': {
  99. 'url': 'http://example.com',
  100. 'env': {
  101. 'REMOTE_ADDR': '127.0.0.1',
  102. }
  103. },
  104. 'sentry.interfaces.User': {
  105. 'ip_address': '192.168.0.1',
  106. },
  107. }))
  108. data = manager.normalize()
  109. assert data['sentry.interfaces.User']['ip_address'] == '192.168.0.1'
  110. def test_does_not_default_invalid_ip_address(self):
  111. manager = EventManager(self.make_event(**{
  112. 'sentry.interfaces.Http': {
  113. 'url': 'http://example.com',
  114. 'env': {
  115. 'REMOTE_ADDR': '127.0.0.1, 192.168.0.1',
  116. }
  117. }
  118. }))
  119. data = manager.normalize()
  120. assert 'sentry.interfaces.User' not in data
  121. def test_platform_is_saved(self):
  122. manager = EventManager(self.make_event(**{
  123. 'sentry.interfaces.AppleCrashReport': {
  124. 'crash': {},
  125. 'binary_images': []
  126. }
  127. }))
  128. manager.normalize()
  129. event = manager.save(1)
  130. assert 'sentry.interfacse.AppleCrashReport' not in event.interfaces
  131. def test_ephemral_interfaces_removed_on_save(self):
  132. manager = EventManager(self.make_event(platform='python'))
  133. event = manager.save(1)
  134. group = event.group
  135. assert group.platform == 'python'
  136. assert event.platform == 'python'
  137. def test_dupe_message_id(self):
  138. event_id = 'a' * 32
  139. manager = EventManager(self.make_event(event_id=event_id))
  140. manager.save(1)
  141. assert Event.objects.count() == 1
  142. # ensure that calling it again doesn't raise a db error
  143. manager = EventManager(self.make_event(event_id=event_id))
  144. manager.save(1)
  145. assert Event.objects.count() == 1
  146. def test_updates_group(self):
  147. manager = EventManager(self.make_event(
  148. message='foo', event_id='a' * 32,
  149. checksum='a' * 32,
  150. ))
  151. event = manager.save(1)
  152. manager = EventManager(self.make_event(
  153. message='foo bar', event_id='b' * 32,
  154. checksum='a' * 32,
  155. ))
  156. with self.tasks():
  157. event2 = manager.save(1)
  158. group = Group.objects.get(id=event.group_id)
  159. assert group.times_seen == 2
  160. assert group.last_seen.replace(microsecond=0) == event.datetime.replace(microsecond=0)
  161. assert group.message == event2.message
  162. assert group.data.get('type') == 'default'
  163. assert group.data.get('metadata') == {
  164. 'title': 'foo bar',
  165. }
  166. def test_updates_group_with_fingerprint(self):
  167. manager = EventManager(self.make_event(
  168. message='foo', event_id='a' * 32,
  169. fingerprint=['a' * 32],
  170. ))
  171. with self.tasks():
  172. event = manager.save(1)
  173. manager = EventManager(self.make_event(
  174. message='foo bar', event_id='b' * 32,
  175. fingerprint=['a' * 32],
  176. ))
  177. with self.tasks():
  178. event2 = manager.save(1)
  179. group = Group.objects.get(id=event.group_id)
  180. assert group.times_seen == 2
  181. assert group.last_seen.replace(microsecond=0) == event.datetime.replace(microsecond=0)
  182. assert group.message == event2.message
  183. def test_differentiates_with_fingerprint(self):
  184. manager = EventManager(self.make_event(
  185. message='foo', event_id='a' * 32,
  186. fingerprint=['{{ default }}', 'a' * 32],
  187. ))
  188. with self.tasks():
  189. manager.normalize()
  190. event = manager.save(1)
  191. manager = EventManager(self.make_event(
  192. message='foo bar', event_id='b' * 32,
  193. fingerprint=['a' * 32],
  194. ))
  195. with self.tasks():
  196. manager.normalize()
  197. event2 = manager.save(1)
  198. assert event.group_id != event2.group_id
  199. def test_unresolves_group(self):
  200. # N.B. EventManager won't unresolve the group unless the event2 has a
  201. # later timestamp than event1. MySQL doesn't support microseconds.
  202. manager = EventManager(self.make_event(
  203. event_id='a' * 32, checksum='a' * 32,
  204. timestamp=1403007314,
  205. ))
  206. with self.tasks():
  207. event = manager.save(1)
  208. group = Group.objects.get(id=event.group_id)
  209. group.status = GroupStatus.RESOLVED
  210. group.save()
  211. assert group.is_resolved()
  212. manager = EventManager(self.make_event(
  213. event_id='b' * 32, checksum='a' * 32,
  214. timestamp=1403007345,
  215. ))
  216. event2 = manager.save(1)
  217. assert event.group_id == event2.group_id
  218. group = Group.objects.get(id=group.id)
  219. assert not group.is_resolved()
  220. @patch('sentry.event_manager.plugin_is_regression')
  221. def test_does_not_unresolve_group(self, plugin_is_regression):
  222. # N.B. EventManager won't unresolve the group unless the event2 has a
  223. # later timestamp than event1. MySQL doesn't support microseconds.
  224. plugin_is_regression.return_value = False
  225. manager = EventManager(self.make_event(
  226. event_id='a' * 32, checksum='a' * 32,
  227. timestamp=1403007314,
  228. ))
  229. with self.tasks():
  230. event = manager.save(1)
  231. group = Group.objects.get(id=event.group_id)
  232. group.status = GroupStatus.RESOLVED
  233. group.save()
  234. assert group.is_resolved()
  235. manager = EventManager(self.make_event(
  236. event_id='b' * 32, checksum='a' * 32,
  237. timestamp=1403007315,
  238. ))
  239. event2 = manager.save(1)
  240. assert event.group_id == event2.group_id
  241. group = Group.objects.get(id=group.id)
  242. assert group.is_resolved()
  243. @patch('sentry.tasks.activity.send_activity_notifications.delay')
  244. @patch('sentry.event_manager.plugin_is_regression')
  245. def test_marks_as_unresolved_with_new_release(self, plugin_is_regression,
  246. mock_send_activity_notifications_delay):
  247. plugin_is_regression.return_value = True
  248. old_release = Release.objects.create(
  249. version='a',
  250. organization_id=self.project.organization_id,
  251. date_added=timezone.now() - timedelta(minutes=30),
  252. )
  253. old_release.add_project(self.project)
  254. manager = EventManager(self.make_event(
  255. event_id='a' * 32,
  256. checksum='a' * 32,
  257. timestamp=time() - 50000, # need to work around active_at
  258. release=old_release.version,
  259. ))
  260. event = manager.save(1)
  261. group = event.group
  262. group.update(status=GroupStatus.RESOLVED)
  263. resolution = GroupResolution.objects.create(
  264. release=old_release,
  265. group=group,
  266. )
  267. activity = Activity.objects.create(
  268. group=group,
  269. project=group.project,
  270. type=Activity.SET_RESOLVED_IN_RELEASE,
  271. ident=resolution.id,
  272. data={'version': ''},
  273. )
  274. manager = EventManager(self.make_event(
  275. event_id='b' * 32,
  276. checksum='a' * 32,
  277. timestamp=time(),
  278. release=old_release.version,
  279. ))
  280. event = manager.save(1)
  281. assert event.group_id == group.id
  282. group = Group.objects.get(id=group.id)
  283. assert group.status == GroupStatus.RESOLVED
  284. activity = Activity.objects.get(id=activity.id)
  285. assert activity.data['version'] == ''
  286. assert GroupResolution.objects.filter(group=group).exists()
  287. manager = EventManager(self.make_event(
  288. event_id='c' * 32,
  289. checksum='a' * 32,
  290. timestamp=time(),
  291. release='b',
  292. ))
  293. event = manager.save(1)
  294. assert event.group_id == group.id
  295. group = Group.objects.get(id=group.id)
  296. assert group.status == GroupStatus.UNRESOLVED
  297. activity = Activity.objects.get(id=activity.id)
  298. assert activity.data['version'] == 'b'
  299. assert not GroupResolution.objects.filter(group=group).exists()
  300. activity = Activity.objects.get(
  301. group=group,
  302. type=Activity.SET_REGRESSION,
  303. )
  304. mock_send_activity_notifications_delay.assert_called_once_with(
  305. activity.id
  306. )
  307. @patch('sentry.models.Group.is_resolved')
  308. def test_unresolves_group_with_auto_resolve(self, mock_is_resolved):
  309. mock_is_resolved.return_value = False
  310. manager = EventManager(self.make_event(
  311. event_id='a' * 32, checksum='a' * 32,
  312. timestamp=1403007314,
  313. ))
  314. with self.tasks():
  315. event = manager.save(1)
  316. mock_is_resolved.return_value = True
  317. manager = EventManager(self.make_event(
  318. event_id='b' * 32, checksum='a' * 32,
  319. timestamp=1403007414,
  320. ))
  321. with self.tasks():
  322. event2 = manager.save(1)
  323. assert event.group_id == event2.group_id
  324. group = Group.objects.get(id=event.group.id)
  325. assert group.active_at == event2.datetime != event.datetime
  326. def test_long_culprit(self):
  327. manager = EventManager(self.make_event(
  328. culprit='x' * (MAX_CULPRIT_LENGTH + 1),
  329. ))
  330. data = manager.normalize()
  331. assert len(data['culprit']) == MAX_CULPRIT_LENGTH
  332. def test_long_message(self):
  333. manager = EventManager(self.make_event(
  334. message='x' * (settings.SENTRY_MAX_MESSAGE_LENGTH + 1),
  335. ))
  336. data = manager.normalize()
  337. assert len(data['sentry.interfaces.Message']['message']) == \
  338. settings.SENTRY_MAX_MESSAGE_LENGTH
  339. def test_default_version(self):
  340. manager = EventManager(self.make_event())
  341. data = manager.normalize()
  342. assert data['version'] == '5'
  343. def test_explicit_version(self):
  344. manager = EventManager(self.make_event(), '6')
  345. data = manager.normalize()
  346. assert data['version'] == '6'
  347. def test_first_release(self):
  348. manager = EventManager(self.make_event(release='1.0'))
  349. event = manager.save(1)
  350. group = event.group
  351. assert group.first_release.version == '1.0'
  352. manager = EventManager(self.make_event(release='2.0'))
  353. event = manager.save(1)
  354. group = event.group
  355. assert group.first_release.version == '1.0'
  356. def test_release_project_slug(self):
  357. project = self.create_project(name='foo')
  358. release = Release.objects.create(
  359. version='foo-1.0',
  360. organization=project.organization
  361. )
  362. release.add_project(project)
  363. manager = EventManager(self.make_event(release='1.0'))
  364. event = manager.save(project.id)
  365. group = event.group
  366. assert group.first_release.version == 'foo-1.0'
  367. release_tag = [v for k, v in event.tags if k == 'sentry:release'][0]
  368. assert release_tag == 'foo-1.0'
  369. manager = EventManager(self.make_event(release='2.0'))
  370. event = manager.save(project.id)
  371. group = event.group
  372. assert group.first_release.version == 'foo-1.0'
  373. def test_release_project_slug_long(self):
  374. project = self.create_project(name='foo')
  375. release = Release.objects.create(
  376. version='foo-%s' % ('a' * 60,),
  377. organization=project.organization
  378. )
  379. release.add_project(project)
  380. manager = EventManager(self.make_event(release=('a' * 61)))
  381. event = manager.save(project.id)
  382. group = event.group
  383. assert group.first_release.version == 'foo-%s' % ('a' * 60,)
  384. release_tag = [v for k, v in event.tags if k == 'sentry:release'][0]
  385. assert release_tag == 'foo-%s' % ('a' * 60,)
  386. def test_group_release_no_env(self):
  387. manager = EventManager(self.make_event(release='1.0'))
  388. event = manager.save(1)
  389. release = Release.objects.get(version='1.0', projects=event.project_id)
  390. assert GroupRelease.objects.filter(
  391. release_id=release.id,
  392. group_id=event.group_id,
  393. environment='',
  394. ).exists()
  395. # ensure we're not erroring on second creation
  396. manager = EventManager(self.make_event(release='1.0'))
  397. manager.save(1)
  398. def test_group_release_with_env(self):
  399. manager = EventManager(self.make_event(
  400. release='1.0', environment='prod',
  401. event_id='a' * 32))
  402. event = manager.save(1)
  403. release = Release.objects.get(version='1.0', projects=event.project_id)
  404. assert GroupRelease.objects.filter(
  405. release_id=release.id,
  406. group_id=event.group_id,
  407. environment='prod',
  408. ).exists()
  409. manager = EventManager(self.make_event(
  410. release='1.0', environment='staging',
  411. event_id='b' * 32))
  412. event = manager.save(1)
  413. release = Release.objects.get(version='1.0', projects=event.project_id)
  414. assert GroupRelease.objects.filter(
  415. release_id=release.id,
  416. group_id=event.group_id,
  417. environment='staging',
  418. ).exists()
  419. def test_bad_logger(self):
  420. manager = EventManager(self.make_event(logger='foo bar'))
  421. data = manager.normalize()
  422. assert data['logger'] == DEFAULT_LOGGER_NAME
  423. @pytest.mark.xfail
  424. def test_record_frequencies(self):
  425. project = self.project
  426. manager = EventManager(self.make_event())
  427. event = manager.save(project)
  428. assert tsdb.get_most_frequent(
  429. tsdb.models.frequent_issues_by_project,
  430. (event.project.id,),
  431. event.datetime,
  432. ) == {
  433. event.project.id: [
  434. (event.group_id, 1.0),
  435. ],
  436. }
  437. assert tsdb.get_most_frequent(
  438. tsdb.models.frequent_projects_by_organization,
  439. (event.project.organization_id,),
  440. event.datetime,
  441. ) == {
  442. event.project.organization_id: [
  443. (event.project_id, 1.0),
  444. ],
  445. }
  446. def test_event_user(self):
  447. manager = EventManager(self.make_event(**{
  448. 'sentry.interfaces.User': {
  449. 'id': '1',
  450. }
  451. }))
  452. manager.normalize()
  453. with self.tasks():
  454. event = manager.save(self.project.id)
  455. assert tsdb.get_distinct_counts_totals(
  456. tsdb.models.users_affected_by_group,
  457. (event.group.id,),
  458. event.datetime,
  459. event.datetime,
  460. ) == {
  461. event.group.id: 1,
  462. }
  463. assert tsdb.get_distinct_counts_totals(
  464. tsdb.models.users_affected_by_project,
  465. (event.project.id,),
  466. event.datetime,
  467. event.datetime,
  468. ) == {
  469. event.project.id: 1,
  470. }
  471. euser = EventUser.objects.get(
  472. project=self.project,
  473. ident='1',
  474. )
  475. assert event.get_tag('sentry:user') == euser.tag_value
  476. # ensure event user is mapped to tags in second attempt
  477. manager = EventManager(self.make_event(**{
  478. 'sentry.interfaces.User': {
  479. 'id': '1',
  480. 'name': 'jane',
  481. }
  482. }))
  483. manager.normalize()
  484. with self.tasks():
  485. event = manager.save(self.project.id)
  486. euser = EventUser.objects.get(id=euser.id)
  487. assert event.get_tag('sentry:user') == euser.tag_value
  488. assert euser.name == 'jane'
  489. assert euser.ident == '1'
  490. def test_event_user_unicode_identifier(self):
  491. manager = EventManager(self.make_event(**{
  492. 'sentry.interfaces.User': {
  493. 'username': u'foô'
  494. }
  495. }))
  496. manager.normalize()
  497. with self.tasks():
  498. manager.save(self.project.id)
  499. euser = EventUser.objects.get(
  500. project=self.project,
  501. )
  502. assert euser.username == u'foô'
  503. def test_environment(self):
  504. manager = EventManager(self.make_event(**{
  505. 'environment': 'beta',
  506. }))
  507. manager.normalize()
  508. event = manager.save(self.project.id)
  509. assert dict(event.tags).get('environment') == 'beta'
  510. def test_default_fingerprint(self):
  511. manager = EventManager(self.make_event())
  512. manager.normalize()
  513. event = manager.save(self.project.id)
  514. assert event.data.get('fingerprint') == ['{{ default }}']
  515. def test_default_event_type(self):
  516. manager = EventManager(self.make_event(message='foo bar'))
  517. data = manager.normalize()
  518. assert data['type'] == 'default'
  519. event = manager.save(self.project.id)
  520. group = event.group
  521. assert group.data.get('type') == 'default'
  522. assert group.data.get('metadata') == {
  523. 'title': 'foo bar',
  524. }
  525. def test_message_event_type(self):
  526. manager = EventManager(self.make_event(**{
  527. 'message': '',
  528. 'sentry.interfaces.Message': {
  529. 'formatted': 'foo bar',
  530. 'message': 'foo %s',
  531. 'params': ['bar'],
  532. }
  533. }))
  534. data = manager.normalize()
  535. assert data['type'] == 'default'
  536. event = manager.save(self.project.id)
  537. group = event.group
  538. assert group.data.get('type') == 'default'
  539. assert group.data.get('metadata') == {
  540. 'title': 'foo bar',
  541. }
  542. def test_error_event_type(self):
  543. manager = EventManager(self.make_event(**{
  544. 'sentry.interfaces.Exception': {
  545. 'values': [{
  546. 'type': 'Foo',
  547. 'value': 'bar',
  548. }],
  549. },
  550. }))
  551. data = manager.normalize()
  552. assert data['type'] == 'error'
  553. event = manager.save(self.project.id)
  554. group = event.group
  555. assert group.data.get('type') == 'error'
  556. assert group.data.get('metadata') == {
  557. 'type': 'Foo',
  558. 'value': 'bar',
  559. }
  560. def test_csp_event_type(self):
  561. manager = EventManager(self.make_event(**{
  562. 'sentry.interfaces.Csp': {
  563. 'effective_directive': 'script-src',
  564. 'blocked_uri': 'http://example.com',
  565. },
  566. }))
  567. data = manager.normalize()
  568. assert data['type'] == 'csp'
  569. event = manager.save(self.project.id)
  570. group = event.group
  571. assert group.data.get('type') == 'csp'
  572. assert group.data.get('metadata') == {
  573. 'directive': 'script-src',
  574. 'uri': 'example.com',
  575. 'message': "Blocked 'script' from 'example.com'",
  576. }
  577. def test_sdk(self):
  578. manager = EventManager(self.make_event(**{
  579. 'sdk': {
  580. 'name': 'sentry-unity',
  581. 'version': '1.0',
  582. },
  583. }))
  584. manager.normalize()
  585. event = manager.save(self.project.id)
  586. assert event.data['sdk'] == {
  587. 'name': 'sentry-unity',
  588. 'version': '1.0',
  589. }
  590. def test_no_message(self):
  591. # test that the message is handled gracefully
  592. manager = EventManager(self.make_event(**{
  593. 'message': None,
  594. 'sentry.interfaces.Message': {
  595. 'message': 'hello world',
  596. },
  597. }))
  598. manager.normalize()
  599. event = manager.save(self.project.id)
  600. assert event.message == 'hello world'
  601. def test_bad_message(self):
  602. # test that the message is handled gracefully
  603. manager = EventManager(self.make_event(**{
  604. 'message': 1234,
  605. }))
  606. manager.normalize()
  607. event = manager.save(self.project.id)
  608. assert event.message == '1234'
  609. assert event.data['sentry.interfaces.Message'] == {
  610. 'message': '1234',
  611. }
  612. def test_message_attribute_goes_to_interface(self):
  613. manager = EventManager(self.make_event(**{
  614. 'message': 'hello world',
  615. }))
  616. manager.normalize()
  617. event = manager.save(self.project.id)
  618. assert event.data['sentry.interfaces.Message'] == {
  619. 'message': 'hello world',
  620. }
  621. def test_message_attribute_goes_to_formatted(self):
  622. manager = EventManager(self.make_event(**{
  623. 'message': 'world hello',
  624. 'sentry.interfaces.Message': {
  625. 'message': 'hello world',
  626. },
  627. }))
  628. manager.normalize()
  629. event = manager.save(self.project.id)
  630. assert event.data['sentry.interfaces.Message'] == {
  631. 'message': 'hello world',
  632. 'formatted': 'world hello',
  633. }
  634. class GetHashesFromEventTest(TestCase):
  635. @patch('sentry.interfaces.stacktrace.Stacktrace.compute_hashes')
  636. @patch('sentry.interfaces.http.Http.compute_hashes')
  637. def test_stacktrace_wins_over_http(self, http_comp_hash, stack_comp_hash):
  638. # this was a regression, and a very important one
  639. http_comp_hash.return_value = [['baz']]
  640. stack_comp_hash.return_value = [['foo', 'bar']]
  641. event = Event(
  642. data={
  643. 'sentry.interfaces.Stacktrace': {
  644. 'frames': [{
  645. 'lineno': 1,
  646. 'filename': 'foo.py',
  647. }],
  648. },
  649. 'sentry.interfaces.Http': {
  650. 'url': 'http://example.com'
  651. },
  652. },
  653. platform='python',
  654. message='Foo bar',
  655. )
  656. hashes = get_hashes_for_event(event)
  657. assert len(hashes) == 1
  658. hash_one = hashes[0]
  659. stack_comp_hash.assert_called_once_with('python')
  660. assert not http_comp_hash.called
  661. assert hash_one == ['foo', 'bar']
  662. @patch('sentry.interfaces.stacktrace.Stacktrace.compute_hashes')
  663. @patch('sentry.interfaces.message.Message.compute_hashes')
  664. def test_stacktrace_hash_also_includes_message(self, message_comp_hash, stack_comp_hash):
  665. # this tests the temporary hack in get_hashes_for_event_with_reason
  666. message_comp_hash.return_value = [['baz']]
  667. stack_comp_hash.return_value = [['foo', 'bar']]
  668. event = Event(
  669. data={
  670. 'sentry.interfaces.Stacktrace': {
  671. 'frames': [{
  672. 'lineno': 1,
  673. 'filename': 'foo.py',
  674. }],
  675. },
  676. 'sentry.interfaces.Message': {
  677. 'message': 'abc'
  678. },
  679. },
  680. platform='python',
  681. message='Foo bar',
  682. )
  683. hashes = get_hashes_for_event(event)
  684. assert len(hashes) == 1
  685. hash_one = hashes[0]
  686. stack_comp_hash.assert_called_once_with('python')
  687. message_comp_hash.assert_called_once_with('python')
  688. assert hash_one == ['baz', 'foo', 'bar']
  689. class GetHashesFromFingerprintTest(TestCase):
  690. def test_default_value(self):
  691. event = Event(
  692. data={
  693. 'sentry.interfaces.Stacktrace': {
  694. 'frames': [{
  695. 'lineno': 1,
  696. 'filename': 'foo.py',
  697. }, {
  698. 'lineno': 1,
  699. 'filename': 'foo.py',
  700. 'in_app': True,
  701. }],
  702. },
  703. 'sentry.interfaces.Http': {
  704. 'url': 'http://example.com'
  705. },
  706. },
  707. platform='python',
  708. message='Foo bar',
  709. )
  710. fp_checksums = get_hashes_from_fingerprint(event, ["{{default}}"])
  711. def_checksums = get_hashes_for_event(event)
  712. assert def_checksums == fp_checksums
  713. def test_custom_values(self):
  714. event = Event(
  715. data={
  716. 'sentry.interfaces.Stacktrace': {
  717. 'frames': [{
  718. 'lineno': 1,
  719. 'filename': 'foo.py',
  720. }, {
  721. 'lineno': 1,
  722. 'filename': 'foo.py',
  723. 'in_app': True,
  724. }],
  725. },
  726. 'sentry.interfaces.Http': {
  727. 'url': 'http://example.com'
  728. },
  729. },
  730. platform='python',
  731. message='Foo bar',
  732. )
  733. fp_checksums = get_hashes_from_fingerprint(event, ["{{default}}", "custom"])
  734. def_checksums = get_hashes_for_event(event)
  735. assert len(fp_checksums) == len(def_checksums)
  736. assert def_checksums != fp_checksums
  737. class GenerateCulpritTest(TestCase):
  738. def test_with_exception_interface(self):
  739. data = {
  740. 'sentry.interfaces.Exception': {
  741. 'values': [{
  742. 'stacktrace': {
  743. 'frames': [{
  744. 'lineno': 1,
  745. 'filename': 'foo.py',
  746. }, {
  747. 'lineno': 1,
  748. 'filename': 'bar.py',
  749. 'in_app': True,
  750. }],
  751. }
  752. }]
  753. },
  754. 'sentry.interfaces.Stacktrace': {
  755. 'frames': [{
  756. 'lineno': 1,
  757. 'filename': 'NOTME.py',
  758. }, {
  759. 'lineno': 1,
  760. 'filename': 'PLZNOTME.py',
  761. 'in_app': True,
  762. }],
  763. },
  764. 'sentry.interfaces.Http': {
  765. 'url': 'http://example.com'
  766. },
  767. }
  768. assert generate_culprit(data) == 'bar.py in ?'
  769. def test_with_missing_exception_interface(self):
  770. data = {
  771. 'sentry.interfaces.Stacktrace': {
  772. 'frames': [{
  773. 'lineno': 1,
  774. 'filename': 'NOTME.py',
  775. }, {
  776. 'lineno': 1,
  777. 'filename': 'PLZNOTME.py',
  778. 'in_app': True,
  779. }],
  780. },
  781. 'sentry.interfaces.Http': {
  782. 'url': 'http://example.com'
  783. },
  784. }
  785. assert generate_culprit(data) == 'PLZNOTME.py in ?'
  786. def test_with_empty_stacktrace(self):
  787. data = {
  788. 'sentry.interfaces.Stacktrace': None,
  789. 'sentry.interfaces.Http': {
  790. 'url': 'http://example.com'
  791. },
  792. }
  793. assert generate_culprit(data) == 'http://example.com'
  794. def test_with_only_http_interface(self):
  795. data = {
  796. 'sentry.interfaces.Http': {
  797. 'url': 'http://example.com'
  798. },
  799. }
  800. assert generate_culprit(data) == 'http://example.com'
  801. data = {
  802. 'sentry.interfaces.Http': {},
  803. }
  804. assert generate_culprit(data) == ''
  805. def test_empty_data(self):
  806. assert generate_culprit({}) == ''
  807. def test_truncation(self):
  808. data = {
  809. 'sentry.interfaces.Exception': {
  810. 'values': [{
  811. 'stacktrace': {
  812. 'frames': [{
  813. 'filename': 'x' * (MAX_CULPRIT_LENGTH + 1),
  814. }],
  815. }
  816. }],
  817. }
  818. }
  819. assert len(generate_culprit(data)) == MAX_CULPRIT_LENGTH
  820. data = {
  821. 'sentry.interfaces.Stacktrace': {
  822. 'frames': [{
  823. 'filename': 'x' * (MAX_CULPRIT_LENGTH + 1),
  824. }]
  825. }
  826. }
  827. assert len(generate_culprit(data)) == MAX_CULPRIT_LENGTH
  828. data = {
  829. 'sentry.interfaces.Http': {
  830. 'url': 'x' * (MAX_CULPRIT_LENGTH + 1),
  831. }
  832. }
  833. assert len(generate_culprit(data)) == MAX_CULPRIT_LENGTH
  834. def test_md5_from_hash(self):
  835. result = md5_from_hash(['foo', 'bar', u'foô'])
  836. assert result == '6d81588029ed4190110b2779ba952a00'