499 lines
18 KiB
Python
499 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
|
|
import inspect
|
|
import mock
|
|
import pytest
|
|
import raven
|
|
import time
|
|
from socket import socket, AF_INET, SOCK_DGRAM
|
|
from raven.base import Client, ClientState
|
|
from raven.exceptions import RateLimited
|
|
from raven.transport import AsyncTransport
|
|
from raven.utils.stacks import iter_stack_frames
|
|
from raven.utils import six
|
|
from raven.utils.testutils import TestCase
|
|
|
|
|
|
class TempStoreClient(Client):
|
|
def __init__(self, servers=None, **kwargs):
|
|
self.events = []
|
|
super(TempStoreClient, self).__init__(servers=servers, **kwargs)
|
|
|
|
def is_enabled(self):
|
|
return True
|
|
|
|
def send(self, **kwargs):
|
|
self.events.append(kwargs)
|
|
|
|
|
|
class ClientStateTest(TestCase):
|
|
def test_should_try_online(self):
|
|
state = ClientState()
|
|
self.assertEquals(state.should_try(), True)
|
|
|
|
def test_should_try_new_error(self):
|
|
state = ClientState()
|
|
state.status = state.ERROR
|
|
state.last_check = time.time()
|
|
state.retry_number = 1
|
|
self.assertEquals(state.should_try(), False)
|
|
|
|
def test_should_try_time_passed_error(self):
|
|
state = ClientState()
|
|
state.status = state.ERROR
|
|
state.last_check = time.time() - 10
|
|
state.retry_number = 1
|
|
self.assertEquals(state.should_try(), True)
|
|
|
|
def test_set_fail(self):
|
|
state = ClientState()
|
|
state.set_fail()
|
|
self.assertEquals(state.status, state.ERROR)
|
|
self.assertNotEquals(state.last_check, None)
|
|
self.assertEquals(state.retry_number, 1)
|
|
|
|
def test_set_success(self):
|
|
state = ClientState()
|
|
state.status = state.ERROR
|
|
state.last_check = 'foo'
|
|
state.retry_number = 0
|
|
state.set_success()
|
|
self.assertEquals(state.status, state.ONLINE)
|
|
self.assertEquals(state.last_check, None)
|
|
self.assertEquals(state.retry_number, 0)
|
|
|
|
def test_should_try_retry_after(self):
|
|
state = ClientState()
|
|
state.status = state.ERROR
|
|
state.last_check = time.time()
|
|
state.retry_number = 1
|
|
state.retry_after = 1
|
|
self.assertFalse(state.should_try())
|
|
|
|
def test_should_try_retry_after_passed(self):
|
|
state = ClientState()
|
|
state.status = state.ERROR
|
|
state.last_check = time.time() - 1
|
|
state.retry_number = 1
|
|
state.retry_after = 1
|
|
self.assertTrue(state.should_try())
|
|
|
|
|
|
class ClientTest(TestCase):
|
|
def setUp(self):
|
|
self.client = TempStoreClient()
|
|
|
|
def test_first_client_is_singleton(self):
|
|
from raven import base
|
|
base.Raven = None
|
|
|
|
client = Client()
|
|
client2 = Client()
|
|
|
|
assert base.Raven is client
|
|
assert client is not client2
|
|
|
|
@mock.patch('raven.transport.http.HTTPTransport.send')
|
|
@mock.patch('raven.base.ClientState.should_try')
|
|
def test_send_remote_failover(self, should_try, send):
|
|
should_try.return_value = True
|
|
|
|
client = Client(
|
|
dsn='sync+http://public:secret@example.com/1'
|
|
)
|
|
|
|
# test error
|
|
send.side_effect = Exception()
|
|
client.send_remote('sync+http://example.com/api/store', 'foo')
|
|
self.assertEquals(client.state.status, client.state.ERROR)
|
|
|
|
# test recovery
|
|
send.side_effect = None
|
|
client.send_remote('sync+http://example.com/api/store', 'foo')
|
|
self.assertEquals(client.state.status, client.state.ONLINE)
|
|
|
|
@mock.patch('raven.transport.http.HTTPTransport.send')
|
|
@mock.patch('raven.base.ClientState.should_try')
|
|
def test_send_remote_failover_with_retry_after(self, should_try, send):
|
|
should_try.return_value = True
|
|
|
|
client = Client(
|
|
dsn='sync+http://public:secret@example.com/1'
|
|
)
|
|
|
|
# test error
|
|
send.side_effect = RateLimited('foo', 5)
|
|
client.send_remote('sync+http://example.com/api/store', 'foo')
|
|
self.assertEquals(client.state.status, client.state.ERROR)
|
|
self.assertEqual(client.state.retry_after, 5)
|
|
|
|
# test recovery
|
|
send.side_effect = None
|
|
client.send_remote('sync+http://example.com/api/store', 'foo')
|
|
self.assertEquals(client.state.status, client.state.ONLINE)
|
|
self.assertEqual(client.state.retry_after, 0)
|
|
|
|
@mock.patch('raven.base.Client._registry.get_transport')
|
|
@mock.patch('raven.base.ClientState.should_try')
|
|
def test_async_send_remote_failover(self, should_try, get_transport):
|
|
should_try.return_value = True
|
|
async_transport = AsyncTransport()
|
|
async_transport.async_send = async_send = mock.Mock()
|
|
get_transport.return_value = async_transport
|
|
|
|
client = Client(
|
|
servers=['http://example.com'],
|
|
public_key='public',
|
|
secret_key='secret',
|
|
project=1,
|
|
)
|
|
|
|
# test immediate raise of error
|
|
async_send.side_effect = Exception()
|
|
client.send_remote('http://example.com/api/store', 'foo')
|
|
self.assertEquals(client.state.status, client.state.ERROR)
|
|
|
|
# test recovery
|
|
client.send_remote('http://example.com/api/store', 'foo')
|
|
success_cb = async_send.call_args[0][2]
|
|
success_cb()
|
|
self.assertEquals(client.state.status, client.state.ONLINE)
|
|
|
|
# test delayed raise of error
|
|
client.send_remote('http://example.com/api/store', 'foo')
|
|
failure_cb = async_send.call_args[0][3]
|
|
failure_cb(Exception())
|
|
self.assertEquals(client.state.status, client.state.ERROR)
|
|
|
|
@mock.patch('raven.base.Client.send_remote')
|
|
@mock.patch('raven.base.time.time')
|
|
def test_send(self, time, send_remote):
|
|
time.return_value = 1328055286.51
|
|
client = Client(
|
|
servers=['http://example.com'],
|
|
public_key='public',
|
|
secret_key='secret',
|
|
project=1,
|
|
)
|
|
client.send(**{
|
|
'foo': 'bar',
|
|
})
|
|
send_remote.assert_called_once_with(
|
|
url='http://example.com',
|
|
data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='),
|
|
headers={
|
|
'User-Agent': 'raven-python/%s' % (raven.VERSION,),
|
|
'Content-Type': 'application/octet-stream',
|
|
'X-Sentry-Auth': (
|
|
'Sentry sentry_timestamp=1328055286.51, '
|
|
'sentry_client=raven-python/%s, sentry_version=5, '
|
|
'sentry_key=public, '
|
|
'sentry_secret=secret' % (raven.VERSION,))
|
|
},
|
|
)
|
|
|
|
@mock.patch('raven.base.Client.send_remote')
|
|
@mock.patch('raven.base.time.time')
|
|
def test_send_with_auth_header(self, time, send_remote):
|
|
time.return_value = 1328055286.51
|
|
client = Client(
|
|
servers=['http://example.com'],
|
|
public_key='public',
|
|
secret_key='secret',
|
|
project=1,
|
|
)
|
|
client.send(auth_header='foo', **{
|
|
'foo': 'bar',
|
|
})
|
|
send_remote.assert_called_once_with(
|
|
url='http://example.com',
|
|
data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='),
|
|
headers={
|
|
'User-Agent': 'raven-python/%s' % (raven.VERSION,),
|
|
'Content-Type': 'application/octet-stream',
|
|
'X-Sentry-Auth': 'foo'
|
|
},
|
|
)
|
|
|
|
@mock.patch('raven.transport.http.HTTPTransport.send')
|
|
@mock.patch('raven.base.ClientState.should_try')
|
|
def test_raise_exception_on_send_error(self, should_try, _send_remote):
|
|
should_try.return_value = True
|
|
client = Client(
|
|
servers=['sync+http://example.com'],
|
|
public_key='public',
|
|
secret_key='secret',
|
|
project=1,
|
|
)
|
|
|
|
# Test for the default behaviour in which a send error is handled by the client
|
|
_send_remote.side_effect = Exception()
|
|
client.capture('Message', data={}, date=None, time_spent=10,
|
|
extra={}, stack=None, tags=None, message='Test message')
|
|
assert client.state.status == client.state.ERROR
|
|
|
|
# Test for the case in which a send error is raised to the calling frame.
|
|
client = Client(
|
|
servers=['sync+http://example.com'],
|
|
public_key='public',
|
|
secret_key='secret',
|
|
project=1,
|
|
raise_send_errors=True,
|
|
)
|
|
with self.assertRaises(Exception):
|
|
client.capture('Message', data={}, date=None, time_spent=10,
|
|
extra={}, stack=None, tags=None, message='Test message')
|
|
|
|
def test_encode_decode(self):
|
|
data = {'foo': 'bar'}
|
|
encoded = self.client.encode(data)
|
|
self.assertTrue(type(encoded), str)
|
|
self.assertEquals(data, self.client.decode(encoded))
|
|
|
|
def test_dsn(self):
|
|
client = Client(dsn='http://public:secret@example.com/1')
|
|
self.assertEquals(client.servers, ['http://example.com/api/1/store/'])
|
|
self.assertEquals(client.project, '1')
|
|
self.assertEquals(client.public_key, 'public')
|
|
self.assertEquals(client.secret_key, 'secret')
|
|
|
|
def test_dsn_as_first_arg(self):
|
|
client = Client('http://public:secret@example.com/1')
|
|
self.assertEquals(client.servers, ['http://example.com/api/1/store/'])
|
|
self.assertEquals(client.project, '1')
|
|
self.assertEquals(client.public_key, 'public')
|
|
self.assertEquals(client.secret_key, 'secret')
|
|
|
|
def test_slug_in_dsn(self):
|
|
client = Client('http://public:secret@example.com/slug-name')
|
|
self.assertEquals(client.servers, ['http://example.com/api/slug-name/store/'])
|
|
self.assertEquals(client.project, 'slug-name')
|
|
self.assertEquals(client.public_key, 'public')
|
|
self.assertEquals(client.secret_key, 'secret')
|
|
|
|
def test_get_public_dsn(self):
|
|
client = Client('threaded+http://public:secret@example.com/1')
|
|
public_dsn = client.get_public_dsn()
|
|
self.assertEquals(public_dsn, '//public@example.com/1')
|
|
|
|
def test_get_public_dsn_override_scheme(self):
|
|
client = Client('threaded+http://public:secret@example.com/1')
|
|
public_dsn = client.get_public_dsn('https')
|
|
self.assertEquals(public_dsn, 'https://public@example.com/1')
|
|
|
|
def test_explicit_message_on_message_event(self):
|
|
self.client.captureMessage(message='test', data={
|
|
'message': 'foo'
|
|
})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'foo')
|
|
|
|
def test_message_from_kwargs(self):
|
|
try:
|
|
raise ValueError('foo')
|
|
except ValueError:
|
|
self.client.captureException(message='test', data={})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'test')
|
|
|
|
def test_explicit_message_on_exception_event(self):
|
|
try:
|
|
raise ValueError('foo')
|
|
except ValueError:
|
|
self.client.captureException(data={'message': 'foobar'})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'foobar')
|
|
|
|
def test_exception_event(self):
|
|
try:
|
|
raise ValueError('foo')
|
|
except ValueError:
|
|
self.client.captureException()
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'ValueError: foo')
|
|
self.assertTrue('exception' in event)
|
|
exc = event['exception']['values'][0]
|
|
self.assertEquals(exc['type'], 'ValueError')
|
|
self.assertEquals(exc['value'], 'foo')
|
|
self.assertEquals(exc['module'], ValueError.__module__) # this differs in some Python versions
|
|
assert 'stacktrace' not in event
|
|
stacktrace = exc['stacktrace']
|
|
self.assertEquals(len(stacktrace['frames']), 1)
|
|
frame = stacktrace['frames'][0]
|
|
self.assertEquals(frame['abs_path'], __file__.replace('.pyc', '.py'))
|
|
self.assertEquals(frame['filename'], 'tests/base/tests.py')
|
|
self.assertEquals(frame['module'], __name__)
|
|
self.assertEquals(frame['function'], 'test_exception_event')
|
|
self.assertTrue('timestamp' in event)
|
|
|
|
def test_decorator_preserves_function(self):
|
|
@self.client.capture_exceptions
|
|
def test1():
|
|
return 'foo'
|
|
|
|
self.assertEquals(test1(), 'foo')
|
|
|
|
class DecoratorTestException(Exception):
|
|
pass
|
|
|
|
def test_decorator_functionality(self):
|
|
@self.client.capture_exceptions
|
|
def test2():
|
|
raise self.DecoratorTestException()
|
|
|
|
try:
|
|
test2()
|
|
except self.DecoratorTestException:
|
|
pass
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'DecoratorTestException')
|
|
exc = event['exception']['values'][0]
|
|
self.assertEquals(exc['type'], 'DecoratorTestException')
|
|
self.assertEquals(exc['module'], self.DecoratorTestException.__module__)
|
|
stacktrace = exc['stacktrace']
|
|
# this is a wrapped function so two frames are expected
|
|
self.assertEquals(len(stacktrace['frames']), 2)
|
|
frame = stacktrace['frames'][1]
|
|
self.assertEquals(frame['module'], __name__)
|
|
self.assertEquals(frame['function'], 'test2')
|
|
|
|
def test_decorator_filtering(self):
|
|
@self.client.capture_exceptions(self.DecoratorTestException)
|
|
def test3():
|
|
raise Exception()
|
|
|
|
try:
|
|
test3()
|
|
except Exception:
|
|
pass
|
|
|
|
self.assertEquals(len(self.client.events), 0)
|
|
|
|
def test_message_event(self):
|
|
self.client.captureMessage(message='test')
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'test')
|
|
assert 'stacktrace' not in event
|
|
self.assertTrue('timestamp' in event)
|
|
|
|
def test_context(self):
|
|
self.client.context.merge({
|
|
'tags': {'foo': 'bar'},
|
|
})
|
|
try:
|
|
raise ValueError('foo')
|
|
except ValueError:
|
|
self.client.captureException()
|
|
else:
|
|
self.fail('Exception should have been raised')
|
|
|
|
assert len(self.client.events) == 1
|
|
event = self.client.events.pop(0)
|
|
assert event['tags'] == {'foo': 'bar'}
|
|
|
|
def test_stack_explicit_frames(self):
|
|
def bar():
|
|
return inspect.stack()
|
|
|
|
frames = bar()
|
|
|
|
self.client.captureMessage('test', stack=iter_stack_frames(frames))
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'test')
|
|
assert 'stacktrace' in event
|
|
self.assertEquals(len(frames), len(event['stacktrace']['frames']))
|
|
for frame, frame_i in zip(frames, event['stacktrace']['frames']):
|
|
self.assertEquals(frame[0].f_code.co_filename, frame_i['abs_path'])
|
|
self.assertEquals(frame[0].f_code.co_name, frame_i['function'])
|
|
|
|
def test_stack_auto_frames(self):
|
|
self.client.captureMessage('test', stack=True)
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['message'], 'test')
|
|
self.assertTrue('stacktrace' in event)
|
|
self.assertTrue('timestamp' in event)
|
|
|
|
def test_site(self):
|
|
self.client.captureMessage(message='test', data={'site': 'test'})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
assert 'site' in event['tags']
|
|
assert event['tags']['site'] == 'test'
|
|
|
|
def test_implicit_site(self):
|
|
self.client = TempStoreClient(site='foo')
|
|
self.client.captureMessage(message='test')
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
assert 'site' in event['tags']
|
|
assert event['tags']['site'] == 'foo'
|
|
|
|
def test_logger(self):
|
|
self.client.captureMessage(message='test', data={'logger': 'test'})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['logger'], 'test')
|
|
self.assertTrue('timestamp' in event)
|
|
|
|
def test_tags(self):
|
|
self.client.captureMessage(message='test', tags={'logger': 'test'})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
self.assertEquals(event['tags'], {'logger': 'test'})
|
|
|
|
def test_client_extra_context(self):
|
|
self.client.extra = {
|
|
'foo': 'bar',
|
|
'logger': 'baz',
|
|
}
|
|
self.client.captureMessage(message='test', extra={'logger': 'test'})
|
|
|
|
self.assertEquals(len(self.client.events), 1)
|
|
event = self.client.events.pop(0)
|
|
if six.PY3:
|
|
expected = {'logger': "'test'", 'foo': "'bar'"}
|
|
else:
|
|
expected = {'logger': "u'test'", 'foo': "u'bar'"}
|
|
self.assertEquals(event['extra'], expected)
|
|
|
|
|
|
# TODO: Python 3
|
|
@pytest.mark.skipif(str("six.PY3"))
|
|
class ClientUDPTest(TestCase):
|
|
def setUp(self):
|
|
self.server_socket = socket(AF_INET, SOCK_DGRAM)
|
|
self.server_socket.bind(('127.0.0.1', 0))
|
|
self.client = Client(servers=["udp://%s:%s" % self.server_socket.getsockname()], key='BassOmatic')
|
|
|
|
def test_delivery(self):
|
|
self.client.captureMessage('test')
|
|
data, address = self.server_socket.recvfrom(2 ** 16)
|
|
self.assertTrue("\n\n" in data)
|
|
header, payload = data.split("\n\n")
|
|
for substring in ("sentry_timestamp=", "sentry_client="):
|
|
self.assertTrue(substring in header)
|
|
|
|
def tearDown(self):
|
|
self.server_socket.close()
|