From 104f1d8c2140274d6de1daafe7d3256f4a03b18a Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Tue, 28 May 2019 15:08:46 +0100 Subject: [PATCH 1/4] feat(notification): add Python runtime version information to device data adds version string (from platform.python_version()) to device.runtimeVersions.python payload --- bugsnag/configuration.py | 3 +++ bugsnag/notification.py | 4 +++- bugsnag/sessiontracker.py | 1 + tests/test_notification.py | 15 +++++++++++++++ tests/test_sessiontracker.py | 4 ++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bugsnag/configuration.py b/bugsnag/configuration.py index 8fd43931..ca7fba9d 100644 --- a/bugsnag/configuration.py +++ b/bugsnag/configuration.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, division, print_function import os +import platform import socket try: import sysconfig @@ -92,6 +93,8 @@ def __init__(self): else: self.hostname = None + self.runtime_versions = {"python": str(platform.python_version())} + def should_notify(self): # type: () -> bool return self.notify_release_stages is None or \ (isinstance(self.notify_release_stages, (tuple, list)) and diff --git a/bugsnag/notification.py b/bugsnag/notification.py index 3c7b2b2d..aaab7c53 100644 --- a/bugsnag/notification.py +++ b/bugsnag/notification.py @@ -51,6 +51,7 @@ def get_config(key): self.release_stage = get_config("release_stage") self.app_version = get_config("app_version") self.hostname = get_config("hostname") + self.runtime_versions = get_config("runtime_versions") self.send_code = get_config("send_code") self.context = options.pop("context", None) @@ -242,7 +243,8 @@ def _payload(self): "metaData": FilterDict(self.meta_data), "user": FilterDict(self.user), "device": FilterDict({ - "hostname": self.hostname + "hostname": self.hostname, + "runtimeVersions": self.runtime_versions }), "projectRoot": self.config.get("project_root"), "libRoot": self.config.get("lib_root"), diff --git a/bugsnag/sessiontracker.py b/bugsnag/sessiontracker.py index 9173d97c..3ab59141 100644 --- a/bugsnag/sessiontracker.py +++ b/bugsnag/sessiontracker.py @@ -107,6 +107,7 @@ def __deliver(self, sessions): }, 'device': FilterDict({ 'hostname': self.config.get('hostname'), + 'runtimeVersions': self.config.get('runtime_versions') }), 'app': { 'releaseStage': self.config.get('release_stage'), diff --git a/tests/test_notification.py b/tests/test_notification.py index 4534ddb1..8ef54ffa 100644 --- a/tests/test_notification.py +++ b/tests/test_notification.py @@ -141,3 +141,18 @@ def test_traceback_exclude_modules(self): exception = payload['events'][0]['exceptions'][0] first_traceback = exception['stacktrace'][0] self.assertEqual(first_traceback['file'], 'test_notification.py') + + def test_device_data(self): + """ + It should include device data + """ + config = Configuration() + config.hostname = 'test_host_name' + config.runtime_versions = {'python': '9.9.9'} + notification = Notification(Exception("oops"), config, {}) + + payload = json.loads(notification._payload()) + + device = payload['events'][0]['device'] + self.assertEqual('test_host_name', device['hostname']) + self.assertEqual('9.9.9', device['runtimeVersions']['python']) diff --git a/tests/test_sessiontracker.py b/tests/test_sessiontracker.py index 7394fadb..a191979f 100644 --- a/tests/test_sessiontracker.py +++ b/tests/test_sessiontracker.py @@ -1,3 +1,4 @@ +import platform import time from bugsnag import Client @@ -94,6 +95,9 @@ def test_session_tracker_sets_details_from_config(self): self.assertTrue('hostname' in device) self.assertEqual(device['hostname'], client.configuration.get('hostname')) + self.assertTrue('runtimeVersions' in device) + self.assertEqual(device['runtimeVersions']['python'], + platform.python_version()) def test_session_middleware_attaches_session_to_notification(self): client = Client( From 52ec3d19c87b3d9d516682dc12be3b16cc229471 Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Wed, 5 Jun 2019 15:22:36 +0100 Subject: [PATCH 2/4] docs: update changelog for runtime versions --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c322dd..e22d4f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Enhancements + +* Add Python version string to report and session payloads (device.runtimeVersions) + [#179](https://github.com/bugsnag/bugsnag-python/pull/179) + ## 3.5.2 (2019-03-15) ### Fixes From ee7d5b10403a5e9787c0ebe7bf67f7b1d1c7faad Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Wed, 5 Jun 2019 15:26:24 +0100 Subject: [PATCH 3/4] docs: update readme with travis-ci.com link and build shield --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bb56ac2..de1693e5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Bugsnag exception reporter for Python -[![Build status](https://travis-ci.org/bugsnag/bugsnag-python.svg?branch=master)](https://travis-ci.org/bugsnag/bugsnag-python) +[![Build status](https://img.shields.io/travis/bugsnag/bugsnag-python/master.svg?style=flat-square)](https://travis-ci.com/bugsnag/bugsnag-python) [![Documentation](https://img.shields.io/badge/documentation-latest-blue.svg)](https://docs.bugsnag.com/platforms/python/) The Bugsnag exception reporter for Python automatically detects and reports From 86fb16bda966bf254b12e7a2f376f23b64be53b2 Mon Sep 17 00:00:00 2001 From: Tom Longridge Date: Wed, 12 Jun 2019 10:33:33 +0100 Subject: [PATCH 4/4] test: reduce tests to isolate random FilterDict recursion issue --- bugsnag/utils.py | 15 +- test_to_death.sh | 19 + test_to_death_tx.sh | 18 + tests/integrations/test_flask.py | 182 +------- tests/test_client.py | 362 ---------------- tests/test_configuration.py | 107 ----- tests/test_delivery.py | 79 ---- tests/test_handlers.py | 447 ------------------- tests/test_middleware.py | 145 ------- tests/test_notification.py | 158 ------- tests/test_notify.py | 717 ------------------------------- tests/test_sessiontracker.py | 124 ------ tests/test_utils.py | 228 ++-------- 13 files changed, 82 insertions(+), 2519 deletions(-) create mode 100755 test_to_death.sh create mode 100755 test_to_death_tx.sh delete mode 100644 tests/test_client.py delete mode 100644 tests/test_configuration.py delete mode 100644 tests/test_delivery.py delete mode 100644 tests/test_handlers.py delete mode 100644 tests/test_middleware.py delete mode 100644 tests/test_notification.py delete mode 100644 tests/test_notify.py delete mode 100644 tests/test_sessiontracker.py diff --git a/bugsnag/utils.py b/bugsnag/utils.py index 35886780..c18b557f 100644 --- a/bugsnag/utils.py +++ b/bugsnag/utils.py @@ -30,6 +30,7 @@ def __init__(self, keyword_filters=None, **kwargs): super(SanitizingJSONEncoder, self).__init__(**kwargs) def encode(self, obj): + print('=> encode') safe_obj = self._sanitize(obj, False) payload = super(SanitizingJSONEncoder, self).encode(safe_obj) if len(payload) > MAX_PAYLOAD_LENGTH: @@ -42,6 +43,7 @@ def filter_string_values(self, obj, ignored=None): """ Remove any value from the dictionary which match the key filters """ + print('=> filter_string_values: ' + str(obj)) if not ignored: ignored = set() @@ -49,13 +51,15 @@ def filter_string_values(self, obj, ignored=None): ignored = set(ignored) if id(obj) in ignored: + print('!!!!! Recursive: ' + str(id(obj)) + ' -> ' + str(obj)) return self.recursive_value if isinstance(obj, dict): ignored.add(id(obj)) + print('***** Added: ' + str(id(obj)) + ' -> ' + str(obj)) clean_dict = {} - for key, value in six.iteritems(obj): + for key, value in sorted(six.iteritems(obj)): is_string = isinstance(key, six.string_types) if is_string and any(f in key.lower() for f in self.filters): clean_dict[key] = self.filtered_value @@ -86,6 +90,7 @@ def _sanitize(self, obj, trim_strings, ignored=None): Replace recursive values and trim strings longer than MAX_STRING_LENGTH """ + print('=> _sanitize: ' + str(obj)) if not ignored: ignored = set() @@ -93,14 +98,17 @@ def _sanitize(self, obj, trim_strings, ignored=None): ignored = set(ignored) if id(obj) in ignored: + print('!!!!! Recursive: ' + str(id(obj)) + ' -> ' + str(obj)) return self.recursive_value elif isinstance(obj, dict): ignored.add(id(obj)) + print('***** Added: ' + str(id(obj)) + ' -> ' + str(obj)) return self._sanitize_dict(obj, trim_strings, ignored) elif isinstance(obj, (set, tuple, list)): ignored.add(id(obj)) + print('***** Added: ' + str(id(obj)) + ' -> ' + str(obj)) items = [] - for value in obj: + for value in sorted(obj): items.append(self._sanitize(value, trim_strings, ignored)) return items elif trim_strings and isinstance(obj, six.string_types): @@ -113,6 +121,7 @@ def _sanitize_dict_key_value(self, clean_dict, key, clean_value): Safely sets the provided key on the dictionary by coercing the key to a string """ + print('=> _sanitize_dict_key_value: ' + str(clean_dict) + ', key: ' + str(key)) if isinstance(key, six.string_types): clean_dict[key] = clean_value else: @@ -128,10 +137,12 @@ def _sanitize_dict(self, obj, trim_strings, ignored): Trim individual values in an object, applying filtering if the object is a FilterDict """ + print('=> _sanitize_dict: ' + str(obj)) if isinstance(obj, FilterDict): obj = self.filter_string_values(obj) clean_dict = {} + #for key, value in sorted(six.iteritems(obj)): for key, value in six.iteritems(obj): clean_value = self._sanitize(value, trim_strings, ignored) diff --git a/test_to_death.sh b/test_to_death.sh new file mode 100755 index 00000000..0c1af4a5 --- /dev/null +++ b/test_to_death.sh @@ -0,0 +1,19 @@ +# tweak these +TRIES=1000 +#COMMAND="pyenv exec tox -e py35-flask" +COMMAND="nosetests --tests tests.test_utils" + +# tweaked from http://unix.stackexchange.com/a/82602 +n=0 +until [ $n -ge $TRIES ] +do + echo "****************************************************************************" + echo "****************************************************************************" + echo "ATTEMPT $n" + echo "****************************************************************************" + echo "****************************************************************************" + $COMMAND || break + n=$[$n+1] +done + +echo Ended at attempt $n diff --git a/test_to_death_tx.sh b/test_to_death_tx.sh new file mode 100755 index 00000000..02ce4040 --- /dev/null +++ b/test_to_death_tx.sh @@ -0,0 +1,18 @@ +# tweak these +TRIES=1000 +COMMAND="pyenv exec tox -e $1" + +# tweaked from http://unix.stackexchange.com/a/82602 +n=0 +until [ $n -ge $TRIES ] +do + echo "****************************************************************************" + echo "****************************************************************************" + echo "ATTEMPT $n" + echo "****************************************************************************" + echo "****************************************************************************" + $COMMAND || break + n=$[$n+1] +done + +echo Ended at attempt $n diff --git a/tests/integrations/test_flask.py b/tests/integrations/test_flask.py index 0ebc3173..4b8a7b4e 100644 --- a/tests/integrations/test_flask.py +++ b/tests/integrations/test_flask.py @@ -1,4 +1,6 @@ from flask import Flask +import json + from bugsnag.flask import handle_exceptions import bugsnag.notification from tests.utils import IntegrationTest @@ -19,37 +21,6 @@ def setUp(self): release_stage='dev', asynchronous=False) - def test_bugsnag_middleware_working(self): - app = Flask("bugsnag") - - @app.route("/hello") - def hello(): - return "OK" - - handle_exceptions(app) - - resp = app.test_client().get('/hello') - self.assertEqual(resp.data, b'OK') - - self.assertEqual(0, len(self.server.received)) - - def test_bugsnag_crash(self): - app = Flask("bugsnag") - - @app.route("/hello") - def hello(): - raise SentinelError("oops") - - handle_exceptions(app) - app.test_client().get('/hello') - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - self.assertEqual(payload['events'][0]['exceptions'][0]['errorClass'], - 'test_flask.SentinelError') - self.assertEqual(payload['events'][0]['metaData']['request']['url'], - 'http://localhost/hello') - def test_bugsnag_notify(self): app = Flask("bugsnag") @@ -63,151 +34,8 @@ def hello(): self.assertEqual(1, len(self.server.received)) payload = self.server.received[0]['json_body'] - self.assertEqual(payload['events'][0]['metaData']['request']['url'], - 'http://localhost/hello') - - def test_bugsnag_custom_data(self): - meta_data = [{"hello": {"world": "once"}}, - {"again": {"hello": "world"}}] - - app = Flask("bugsnag") - - @app.route("/hello") - def hello(): - bugsnag.configure_request(meta_data=meta_data.pop()) - raise SentinelError("oops") - - handle_exceptions(app) - with app.test_client() as client: - client.get('/hello') - client.get('/hello') - - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertEqual(event['metaData'].get('hello'), None) - self.assertEqual(event['metaData']['again']['hello'], 'world') - - payload = self.server.received[1]['json_body'] - event = payload['events'][0] - self.assertEqual(event['metaData']['hello']['world'], 'once') - self.assertEqual(event['metaData'].get('again'), None) - self.assertEqual(2, len(self.server.received)) - - def test_bugsnag_includes_posted_json_data(self): - app = Flask("bugsnag") - - @app.route("/ajax", methods=["POST"]) - def hello(): - raise SentinelError("oops") - - handle_exceptions(app) - app.test_client().post( - '/ajax', data='{"key": "value"}', content_type='application/json') - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertEqual(event['exceptions'][0]['errorClass'], - 'test_flask.SentinelError') - self.assertEqual(event['metaData']['request']['url'], - 'http://localhost/ajax') - self.assertEqual(event['metaData']['request']['data'], - dict(key='value')) - - def test_bugsnag_includes_request_when_json_malformed(self): - app = Flask("bugsnag") - - @app.route("/ajax", methods=["POST"]) - def hello(): - raise SentinelError("oops") - - handle_exceptions(app) - app.test_client().post( - '/ajax', data='{"key": "value"', content_type='application/json') - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertEqual(event['exceptions'][0]['errorClass'], - 'test_flask.SentinelError') - self.assertEqual(event['metaData']['request']['url'], - 'http://localhost/ajax') - self.assertEqual(event['metaData']['request']['data']['body'], - '{"key": "value"') - - def test_bugsnag_add_metadata_tab(self): - app = Flask("bugsnag") - - @app.route("/form", methods=["PUT"]) - def hello(): - bugsnag.add_metadata_tab("account", {"id": 1, "premium": True}) - bugsnag.add_metadata_tab("account", {"premium": False}) - raise SentinelError("oops") - - handle_exceptions(app) - app.test_client().put( - '/form', data='_data', content_type='application/octet-stream') - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertEqual(event['metaData']['account']['premium'], False) - self.assertEqual(event['metaData']['account']['id'], 1) - - def test_bugsnag_includes_unknown_content_type_posted_data(self): - app = Flask("bugsnag") - - @app.route("/form", methods=["PUT"]) - def hello(): - raise SentinelError("oops") - - handle_exceptions(app) - app.test_client().put( - '/form', data='_data', content_type='application/octet-stream') - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertEqual(event['exceptions'][0]['errorClass'], - 'test_flask.SentinelError') - self.assertEqual(event['metaData']['request']['url'], - 'http://localhost/form') - body = event['metaData']['request']['data']['body'] - self.assertTrue('_data' in body) - - def test_bugsnag_notify_with_custom_context(self): - app = Flask("bugsnag") - - @app.route("/hello") - def hello(): - bugsnag.notify(SentinelError("oops"), - context="custom_context_notification_testing") - return "OK" - - handle_exceptions(app) - app.test_client().get('/hello') - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - self.assertEqual(payload['events'][0]['context'], - 'custom_context_notification_testing') - def test_flask_intergration_includes_middleware_severity(self): - app = Flask("bugsnag") - - @app.route("/test") - def test(): - raise SentinelError("oops") + print(payload) - handle_exceptions(app) - app.test_client().get("/test") - - self.assertEqual(1, len(self.server.received)) - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - self.assertTrue(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "unhandledExceptionMiddleware", - "attributes": { - "framework": "Flask" - } - }) + self.assertEqual(payload['events'][0]['metaData']['request']['url'], + 'http://localhost/hello') diff --git a/tests/test_client.py b/tests/test_client.py deleted file mode 100644 index 9ec987a5..00000000 --- a/tests/test_client.py +++ /dev/null @@ -1,362 +0,0 @@ -import sys - -from bugsnag import Client, Configuration -from tests.utils import IntegrationTest, ScaryException - - -class ClientTest(IntegrationTest): - def setUp(self): - super(ClientTest, self).setUp() - - self.client = Client(api_key='testing client key', - use_ssl=False, endpoint=self.server.address, - asynchronous=False, - install_sys_hook=False) - - # Initialisation - - def test_init_no_configuration(self): - client = Client(install_sys_hook=False) - self.assertTrue(isinstance(client.configuration, Configuration)) - - def test_init_configuration(self): - configuration = Configuration() - client = Client(configuration=configuration, install_sys_hook=False) - - self.assertEqual(client.configuration, configuration) - - def test_init_options(self): - client = Client(api_key='testing client key', install_sys_hook=False) - self.assertEqual(client.configuration.api_key, 'testing client key') - - # Sending Notification - - def test_notify_exception(self): - self.client.notify(Exception('Testing Notify')) - - self.assertEqual(len(self.server.received), 1) - - def test_notify_exc_info(self): - try: - raise Exception('Testing Notify EXC Info') - except Exception: - self.client.notify_exc_info(*sys.exc_info()) - - self.assertEqual(len(self.server.received), 1) - - def test_delivery(self): - c = Configuration() - self.called = False - - class FooDelivery: - - def deliver(foo, config, payload): - self.called = True - - c.configure(delivery=FooDelivery(), api_key='abc') - client = Client(c) - client.notify(Exception('Oh no')) - self.assertTrue(self.called) - self.assertSentReportCount(0) - del self.called - - def test_invalid_delivery(self): - c = Configuration() - c.configure(delivery=44, api_key='abc') - client = Client(c) - client.notify(Exception('Oh no')) - - def test_failed_delivery(self): - c = Configuration() - self.called = False - - class FooDelivery: - - def deliver(foo, config, payload): - self.called = True - raise ScaryException('something gone wrong') - - c.configure(delivery=FooDelivery(), api_key='abc') - client = Client(c) - client.notify(Exception('Oh no')) - self.assertTrue(self.called) - del self.called - - # Capture - - def test_notify_capture(self): - try: - with self.client.capture(): - raise Exception('Testing Notify Context') - except Exception: - pass - - self.assertEqual(len(self.server.received), 1) - - def test_notify_capture_raises(self): - - def foo(): - with self.client.capture(): - raise Exception('Testing Notify Context') - - self.assertRaises(Exception, foo) - self.assertEqual(len(self.server.received), 1) - - def test_notify_capture_options(self): - try: - with self.client.capture(section={'key': 'value'}): - raise Exception('Testing Notify Context') - except Exception: - pass - - self.assertEqual(len(self.server.received), 1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(event['metaData']['section'], { - 'key': 'value' - }) - - def test_notify_capture_change_severity(self): - try: - with self.client.capture(severity='info'): - raise Exception('Testing Notify Context') - except Exception: - pass - - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - - self.assertEqual(event['severity'], "info") - self.assertFalse(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "userContextSetSeverity" - }) - - def test_notify_capture_types(self): - try: - with self.client.capture((ScaryException,)): - raise Exception('Testing Notify Capture Types') - except Exception: - pass - - self.assertSentReportCount(0) - - try: - with self.client.capture((ScaryException,)): - raise ScaryException('Testing Notify Capture Types') - except Exception: - pass - - self.assertSentReportCount(1) - self.assertExceptionName(0, 0, 'tests.utils.ScaryException') - - def test_no_exception_capture(self): - with self.client.capture(): - pass - - self.assertEqual(len(self.server.received), 0) - - def test_capture_decorator(self): - - @self.client.capture - def foo(): - raise Exception('Testing Capture Function') - - try: - foo() - except Exception: - pass - - self.assertSentReportCount(1) - - def test_capture_decorator_mismatch(self): - - @self.client.capture - def foo(): - pass - - self.assertRaises(TypeError, foo, 'bar') - self.assertSentReportCount(1) - - payload = self.server.received[0]['json_body'] - file = payload['events'][0]['exceptions'][0]['stacktrace'][0]['file'] - - self.assertEqual(file, "test_client.py") - - def test_capture_decorator_returns_value(self): - - @self.client.capture - def foo(): - return "300" - - self.assertEqual(foo(), "300") - - def test_capture_decorator_change_severity(self): - @self.client.capture(severity='info') - def foo(): - raise Exception('Testing Capture Function') - - try: - foo(Exception) - except Exception: - pass - - payload = self.server.received[0]['json_body'] - event = payload['events'][0] - - self.assertEqual(event['severity'], "info") - self.assertFalse(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "userContextSetSeverity" - }) - - def test_capture_decorator_raises(self): - - @self.client.capture - def foo(): - raise Exception('Testing Capture Function') - - self.assertRaises(Exception, foo) - - self.assertSentReportCount(1) - - def test_capture_decorator_with_types(self): - - @self.client.capture((ScaryException,)) - def foo(exception_type): - raise exception_type('Testing Capture Function with Types') - - try: - foo(Exception) - except Exception: - pass - - self.assertSentReportCount(0) - - try: - foo(ScaryException) - except Exception: - pass - - self.assertSentReportCount(1) - - def test_capture_decorator_with_class_method(self): - class Test(object): - @self.client.capture() - def foo(self): - raise Exception() - - try: - test = Test() - test.foo() - except Exception: - pass - - self.assertSentReportCount(1) - - # Exception Hook - - def test_exception_hook(self): - try: - raise Exception('Testing excepthook notify') - except Exception: - self.client.excepthook(*sys.exc_info()) - - self.assertEqual(len(self.server.received), 1) - event = self.server.received[0]['json_body']['events'][0] - self.assertEqual(event['severity'], 'error') - - def test_exception_hook_disabled(self): - self.client.configuration.auto_notify = False - - try: - raise Exception('Testing excepthook notify') - except Exception: - self.client.excepthook(*sys.exc_info()) - - self.assertEqual(len(self.server.received), 0) - - def test_installed_except_hook(self): - client = Client() - - # Prevent the existing hook from being called - client.sys_excepthook = None - - self.hooked = None - - def hooked_except_hook(*exc_info): - self.hooked = exc_info - - client.excepthook = hooked_except_hook - - try: - raise Exception('Testing excepthook notify') - except Exception: - sys.excepthook(*sys.exc_info()) - - self.assertEqual(self.hooked[0], Exception) - - def test_installed_except_hook_calls_previous_except_hook(self): - self.hook_ran = False - - def excepthook(*exc_info): - self.hook_ran = True - sys.excepthook = excepthook - - client = Client(auto_notify=False) # noqa - - try: - raise Exception('Testing excepthook notify') - except Exception: - sys.excepthook(*sys.exc_info()) - - self.assertTrue(self.hook_ran) - - def test_unregister_installed_except_hook(self): - # Setup an original except hook - def excepthook(*exc_info): - pass - sys.excepthook = excepthook - - client = Client() - self.assertNotEqual(sys.excepthook, excepthook) - client.uninstall_sys_hook() - self.assertEqual(sys.excepthook, excepthook) - - # Multiple Clients - - def test_multiple_clients_different_keys(self): - client1 = Client(api_key='abc', asynchronous=False, use_ssl=False, - endpoint=self.server.address) - client2 = Client(api_key='456', asynchronous=False, use_ssl=False, - endpoint=self.server.address) - - client1.notify(ScaryException('foo')) - self.assertSentReportCount(1) - - headers = self.server.received[0]['headers'] - - client2.notify(ScaryException('bar')) - self.assertSentReportCount(2) - - headers = self.server.received[1]['headers'] - self.assertEqual(headers['Bugsnag-Api-Key'], '456') - - def test_multiple_clients_one_excepthook(self): - def excepthook(*exc_info): - pass - sys.excepthook = excepthook - - client1 = Client(api_key='abc', asynchronous=False, use_ssl=False, - endpoint=self.server.address) - Client(api_key='456', asynchronous=False, use_ssl=False, - endpoint=self.server.address, install_sys_hook=False) - - self.assertEqual(client1, sys.excepthook.bugsnag_client) - self.assertEqual(client1.sys_excepthook, excepthook) - - # Session Tracking - - def test_session_tracker_object_exists(self): - client = Client() - self.assertTrue(hasattr(client, 'session_tracker')) diff --git a/tests/test_configuration.py b/tests/test_configuration.py deleted file mode 100644 index eb31cfa4..00000000 --- a/tests/test_configuration.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import socket -import unittest - -from bugsnag.configuration import Configuration -from bugsnag.middleware import DefaultMiddleware -from bugsnag.sessiontracker import SessionMiddleware - - -class TestConfiguration(unittest.TestCase): - - def test_get_endpoint_use_ssl(self): - c = Configuration() - c.use_ssl = True - self.assertEqual(c.get_endpoint(), "https://notify.bugsnag.com") - - def test_get_endpoint_no_use_ssl(self): - c = Configuration() - c.use_ssl = False - self.assertEqual(c.get_endpoint(), "http://notify.bugsnag.com") - - def test_custom_get_endpoint_default_ssl(self): - c = Configuration() - c.endpoint = "localhost:1234" - self.assertEqual(c.get_endpoint(), "https://localhost:1234") - - def test_custom_get_endpoint_use_ssl(self): - c = Configuration() - c.use_ssl = True - c.endpoint = "localhost:1234" - self.assertEqual(c.get_endpoint(), "https://localhost:1234") - - def test_custom_get_endpoint_no_use_ssl(self): - c = Configuration() - c.use_ssl = False - c.endpoint = "localhost:1234" - self.assertEqual(c.get_endpoint(), "http://localhost:1234") - - def test_full_custom_get_endpoint(self): - c = Configuration() - c.endpoint = "https://localhost:1234" - self.assertEqual(c.get_endpoint(), "https://localhost:1234") - - def test_full_custom_get_endpoint_use_ssl(self): - c = Configuration() - c.use_ssl = True - c.endpoint = "https://localhost:1234" - self.assertEqual(c.get_endpoint(), "https://localhost:1234") - - def test_full_custom_get_endpoint_no_use_ssl(self): - c = Configuration() - c.use_ssl = False - c.endpoint = "https://localhost:1234" - self.assertEqual(c.get_endpoint(), "http://localhost:1234") - - def test_reads_api_key_from_environ(self): - os.environ['BUGSNAG_API_KEY'] = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' - c = Configuration() - self.assertEqual(c.api_key, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') - self.assertEqual(c.project_root, os.getcwd()) - - def test_should_notify(self): - # Test custom release_stage - c = Configuration() - c.release_stage = "anything" - self.assertTrue(c.should_notify()) - - # Test release_stage in notify_release_stages - c = Configuration() - c.notify_release_stages = ["production"] - c.release_stage = "development" - self.assertFalse(c.should_notify()) - - # Test release_stage in notify_release_stages - c = Configuration() - c.notify_release_stages = ["custom"] - c.release_stage = "custom" - self.assertTrue(c.should_notify()) - - def test_ignore_classes(self): - # Test ignoring a class works - c = Configuration() - c.ignore_classes.append("SystemError") - self.assertTrue(c.should_ignore(SystemError("Example"))) - - c = Configuration() - c.ignore_classes.append("SystemError") - self.assertFalse(c.should_ignore(Exception("Example"))) - - def test_hostname(self): - c = Configuration() - self.assertEqual(c.hostname, socket.gethostname()) - - os.environ["DYNO"] = "YES" - c = Configuration() - self.assertEqual(c.hostname, None) - - def test_session_tracking_defaults(self): - c = Configuration() - self.assertEqual(c.auto_capture_sessions, False) - self.assertEqual(c.session_endpoint, "https://sessions.bugsnag.com") - - def test_default_middleware_location(self): - c = Configuration() - self.assertEqual(c.internal_middleware.stack, - [DefaultMiddleware, SessionMiddleware]) - self.assertEqual(len(c.middleware.stack), 0) diff --git a/tests/test_delivery.py b/tests/test_delivery.py deleted file mode 100644 index baaf37fd..00000000 --- a/tests/test_delivery.py +++ /dev/null @@ -1,79 +0,0 @@ -import sys - -from nose.plugins.skip import SkipTest - -from bugsnag import Configuration -from bugsnag.delivery import (UrllibDelivery, RequestsDelivery, - create_default_delivery) - -from tests.utils import IntegrationTest - - -class DeliveryTest(IntegrationTest): - - def setUp(self): - super(DeliveryTest, self).setUp() - self.config = Configuration() - self.config.configure(api_key='abc', - endpoint=self.server.address, - use_ssl=False, - asynchronous=False) - - def test_urllib_delivery_full_url(self): - self.config.configure(endpoint=self.server.url, use_ssl=None) - UrllibDelivery().deliver(self.config, '{"legit": 4}') - - self.assertSentReportCount(1) - request = self.server.received[0] - self.assertEqual(request['json_body'], {"legit": 4}) - - def test_urllib_delivery(self): - UrllibDelivery().deliver(self.config, '{"legit": 4}') - - self.assertSentReportCount(1) - request = self.server.received[0] - self.assertEqual(request['json_body'], {"legit": 4}) - self.assertEqual(request['headers']['Content-Type'], - 'application/json') - - def test_requests_delivery(self): - if sys.version_info < (2, 7): - raise SkipTest("Requests is incompatible with 2.6") - - try: - import requests # noqa - except ImportError: - raise SkipTest("Requests is not installed") - - RequestsDelivery().deliver(self.config, '{"legit": 4}') - - self.assertSentReportCount(1) - request = self.server.received[0] - self.assertEqual(request['json_body'], {"legit": 4}) - self.assertEqual(request['headers']['Content-Type'], - 'application/json') - - def test_requests_delivery_full_url(self): - if sys.version_info < (2, 7): - raise SkipTest("Requests is incompatible with 2.6") - - try: - import requests # noqa - except ImportError: - raise SkipTest("Requests is not installed") - - self.config.configure(endpoint=self.server.url) - del self.config.use_ssl - RequestsDelivery().deliver(self.config, '{"legit": 4}') - - self.assertSentReportCount(1) - request = self.server.received[0] - self.assertEqual(request['json_body'], {'legit': 4}) - - def test_create_default_delivery(self): - delivery = create_default_delivery() - - if 'requests' in sys.modules: - self.assertTrue(isinstance(delivery, RequestsDelivery)) - else: - self.assertTrue(isinstance(delivery, UrllibDelivery)) diff --git a/tests/test_handlers.py b/tests/test_handlers.py deleted file mode 100644 index b61d89fc..00000000 --- a/tests/test_handlers.py +++ /dev/null @@ -1,447 +0,0 @@ -from functools import wraps -import logging - -from six import u - -from bugsnag.handlers import BugsnagHandler -from bugsnag import Client -import bugsnag - -from tests.utils import IntegrationTest, ScaryException - - -def use_client_logger(func): - @wraps(func) - def wrapped(obj): - client = Client(use_ssl=False, - endpoint=obj.server.address, - api_key='tomatoes', - asynchronous=False) - handler = client.log_handler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - try: - func(obj, handler, logger) - finally: - logger.removeHandler(handler) - - return wrapped - - -class HandlersTest(IntegrationTest): - - def setUp(self): - super(HandlersTest, self).setUp() - bugsnag.configure(use_ssl=False, - endpoint=self.server.address, - api_key='tomatoes', - notify_release_stages=['dev'], - release_stage='dev', - asynchronous=False) - bugsnag.logger.setLevel(logging.INFO) - - def tearDown(self): - super(HandlersTest, self).tearDown() - bugsnag.logger.setLevel(logging.CRITICAL) - - def test_message(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.critical('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('The system is down', exception['message']) - - def test_severity_critical(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.critical('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogCRITICAL', exception['errorClass']) - self.assertEqual('error', event['severity']) - self.assertEqual(logging.CRITICAL, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('CRITICAL'), - event['metaData']['extra data']['levelname']) - - def test_severity_error(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.error('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogERROR', exception['errorClass']) - self.assertEqual('error', event['severity']) - self.assertEqual(logging.ERROR, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('ERROR'), - event['metaData']['extra data']['levelname']) - - def test_severity_warning(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.warning('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogWARNING', exception['errorClass']) - self.assertEqual('warning', event['severity']) - self.assertEqual(logging.WARNING, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('WARNING'), - event['metaData']['extra data']['levelname']) - - def test_severity_info(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.info('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogINFO', exception['errorClass']) - self.assertEqual('info', event['severity']) - self.assertEqual(logging.INFO, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('INFO'), - event['metaData']['extra data']['levelname']) - - def test_levelname_message(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - class MessageFilter(logging.Filter): - - def filter(self, record): - record.levelname = None - return True - - handler.addFilter(MessageFilter()) - logger.info('The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogMessage', exception['errorClass']) - - def test_custom_level(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.log(341, 'The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogLevel 341', exception['errorClass']) - - def test_custom_levelname(self): - handler = BugsnagHandler() - logger = logging.getLogger(__name__) - logger.addHandler(handler) - logging.addLevelName(402, 'OMG') - - logger.log(402, 'The system is down') - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('LogOMG', exception['errorClass']) - self.assertEqual('error', event['severity']) - - def test_exc_info_api_key(self): - handler = BugsnagHandler(api_key='new news') - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - try: - raise ScaryException('Oh no') - except Exception: - logger.exception('The system is down') - - logger.removeHandler(handler) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - headers = self.server.received[0]['headers'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('new news', headers['Bugsnag-Api-Key']) - self.assertEqual(exception['errorClass'], 'tests.utils.ScaryException') - - def test_extra_fields(self): - handler = BugsnagHandler(api_key='new news', - extra_fields={'fruit': ['grapes', 'pears']}) - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.error('A wild tomato appeared', extra={ - 'grapes': 8, 'pears': 2, 'tomatoes': 1 - }) - logger.removeHandler(handler) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(event['metaData']['fruit'], { - 'grapes': 8, 'pears': 2 - }) - - def test_client_metadata_fields(self): - client = Client(use_ssl=False, - endpoint=self.server.address, - api_key='new news', - asynchronous=False) - handler = client.log_handler(extra_fields={ - 'fruit': ['grapes', 'pears'] - }) - logger = logging.getLogger(__name__) - logger.addHandler(handler) - - logger.error('A wild tomato appeared', extra={ - 'grapes': 8, 'pears': 2, 'tomatoes': 1 - }) - logger.removeHandler(handler) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(event['metaData']['fruit'], { - 'grapes': 8, 'pears': 2 - }) - - @use_client_logger - def test_client_message(self, handler, logger): - logger.critical('The system is down') - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual('The system is down', exception['message']) - - @use_client_logger - def test_client_severity_critical(self, handler, logger): - logger.critical('The system is down') - - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual('LogCRITICAL', exception['errorClass']) - self.assertEqual('error', event['severity']) - self.assertEqual(logging.CRITICAL, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('CRITICAL'), - event['metaData']['extra data']['levelname']) - - @use_client_logger - def test_client_severity_error(self, handler, logger): - logger.error('The system is down') - - self.assertSentReportCount(1) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual('LogERROR', exception['errorClass']) - self.assertEqual('error', event['severity']) - self.assertEqual(logging.ERROR, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('ERROR'), - event['metaData']['extra data']['levelname']) - - @use_client_logger - def test_client_severity_warning(self, handler, logger): - logger.warning('The system is down') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual('LogWARNING', exception['errorClass']) - self.assertEqual('warning', event['severity']) - self.assertEqual(logging.WARNING, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('WARNING'), - event['metaData']['extra data']['levelname']) - - @use_client_logger - def test_client_severity_info(self, handler, logger): - logger.info('The system is down') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual('LogINFO', exception['errorClass']) - self.assertEqual('info', event['severity']) - self.assertEqual(logging.INFO, - event['metaData']['extra data']['levelno']) - self.assertEqual(u('INFO'), - event['metaData']['extra data']['levelname']) - - @use_client_logger - def test_client_add_callback(self, handler, logger): - - def some_callback(record, options): - for key in record.meals: - options['meta_data'][key] = record.meals[key] - - handler.add_callback(some_callback) - logger.info('Everything is fine', extra={'meals': { - 'food': {'fruit': ['pear', 'grape']}, - 'drinks': {'free': 'water'} - }}) - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(event['metaData']['food'], { - 'fruit': ['pear', 'grape'] - }) - self.assertEqual(event['metaData']['drinks'], { - 'free': 'water' - }) - - @use_client_logger - def test_client_remove_callback(self, handler, logger): - - def some_callback(record, options): - options['meta_data']['tab'] = {'key': 'value'} - - def some_other_callback(record, options): - options['meta_data']['tab2'] = {'key': 'value'} - - handler.add_callback(some_callback) - handler.add_callback(some_other_callback) - handler.remove_callback(some_callback) - logger.info('Everything is fine') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertTrue('tab' not in event['metaData']) - self.assertEqual(event['metaData']['tab2'], { - 'key': 'value' - }) - - @use_client_logger - def test_client_clear_callbacks(self, handler, logger): - - def some_callback(record, options): - options['meta_data']['tab'] = {'key': 'value'} - - def some_other_callback(record, options): - options['meta_data']['tab2'] = {'key': 'value'} - - handler.add_callback(some_callback) - handler.add_callback(some_other_callback) - handler.clear_callbacks() - logger.info('Everything is fine') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertTrue('tab' not in event['metaData']) - self.assertTrue('tab2' not in event['metaData']) - - @use_client_logger - def test_client_crashing_callback(self, handler, logger): - - def some_callback(record, options): - options['meta_data']['tab'] = {'key': 'value'} - raise ScaryException('Oh dear') - - def some_other_callback(record, options): - options['meta_data']['tab']['key2'] = 'other value' - - handler.add_callback(some_callback) - handler.add_callback(some_other_callback) - logger.info('Everything is fine') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(event['metaData']['tab'], { - 'key': 'value', 'key2': 'other value' - }) - - @use_client_logger - def test_client_callback_exception(self, handler, logger): - - def exception_replacing_callback(record, options): - options['exception'] = ScaryException('replacement') - - handler.add_callback(exception_replacing_callback) - logger.info('Everything is fine') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual(exception['errorClass'], 'tests.utils.ScaryException') - self.assertEqual(exception['message'], 'replacement') - - @use_client_logger - def test_client_callback_exception_metadata(self, handler, logger): - - def exception_replacing_callback(record, options): - options['exception'] = 'metadata' - - handler.add_callback(exception_replacing_callback) - logger.info('Everything is fine') - - self.assertSentReportCount(1) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - - self.assertEqual(exception['errorClass'], 'LogINFO') - self.assertEqual(exception['message'], 'Everything is fine') - self.assertEqual(event['metaData']['custom'], - {'exception': 'metadata'}) diff --git a/tests/test_middleware.py b/tests/test_middleware.py deleted file mode 100644 index cd35ce05..00000000 --- a/tests/test_middleware.py +++ /dev/null @@ -1,145 +0,0 @@ -import unittest - -from bugsnag.middleware import MiddlewareStack - - -class TestMiddlewareClass(object): - def __init__(self, callback): - self.callback = callback - self.char = None - - def __call__(self, item): - item.append(self.char) - self.callback(item) - - -class TestMiddlewareClassA(TestMiddlewareClass): - def __init__(self, callback): - TestMiddlewareClass.__init__(self, callback) - self.char = 'A' - - -class TestMiddlewareClassB(TestMiddlewareClass): - def __init__(self, callback): - TestMiddlewareClass.__init__(self, callback) - self.char = 'B' - - -class TestMiddlewareClassC(TestMiddlewareClass): - def __init__(self, callback): - TestMiddlewareClass.__init__(self, callback) - self.char = 'C' - - -class TestMiddlewareReturning(object): - def __init__(self, callback): - pass - - def __call__(self, item): - return - - -class TestMiddleware(unittest.TestCase): - - def test_order_of_middleware(self): - - a = [] - - m = MiddlewareStack() - - m.before_notify(lambda _: a.append(1)) - m.before_notify(lambda _: a.append(2)) - - m.after_notify(lambda _: a.append(4)) - m.after_notify(lambda _: a.append(3)) - - m.run(None, lambda: None) - - self.assertEqual(a, [1, 2, 3, 4]) - - def test_before_notify_returning_false(self): - - a = [] - - m = MiddlewareStack() - - m.before_notify(lambda _: False) - m.before_notify(lambda _: a.append(1)) - - m.run(None, lambda: a.append(2)) - - self.assertEqual(a, []) - - def test_before_exception_handling(self): - - a = [] - - m = MiddlewareStack() - m.before_notify(lambda _: a.penned(1)) - m.run(None, lambda: a.append(2)) - - self.assertEqual(a, [2]) - - def test_after_exception_handling(self): - a = [] - - m = MiddlewareStack() - m.after_notify(lambda _: a.penned(1)) - m.run(None, lambda: a.append(2)) - - self.assertEqual(a, [2]) - - def test_insert_before_ordering(self): - a = [] - - m = MiddlewareStack() - m.append(TestMiddlewareClassA) - m.append(TestMiddlewareClassB) - m.insert_before(TestMiddlewareClassA, TestMiddlewareClassC) - m.run(a, lambda: a.append('Callback')) - - self.assertEqual(a, ['C', 'A', 'B', 'Callback']) - - def test_insert_before_default_ordering(self): - a = [] - - m = MiddlewareStack() - m.append(TestMiddlewareClassA) - m.append(TestMiddlewareClassB) - m.insert_before(TestMiddlewareClass, TestMiddlewareClassC) - m.run(a, lambda: a.append('Callback')) - - self.assertEqual(a, ['A', 'B', 'C', 'Callback']) - - def test_insert_after_ordering(self): - a = [] - - m = MiddlewareStack() - m.append(TestMiddlewareClassA) - m.append(TestMiddlewareClassB) - m.insert_after(TestMiddlewareClassA, TestMiddlewareClassC) - m.run(a, lambda: a.append('Callback')) - - self.assertEqual(a, ['A', 'C', 'B', 'Callback']) - - def test_insert_after_default_ordering(self): - a = [] - - m = MiddlewareStack() - m.append(TestMiddlewareClassA) - m.append(TestMiddlewareClassB) - m.insert_after(TestMiddlewareClass, TestMiddlewareClassC) - m.run(a, lambda: a.append('Callback')) - - self.assertEqual(a, ['A', 'B', 'C', 'Callback']) - - def test_callback_not_run_if_middleware_returns(self): - a = [] - - m = MiddlewareStack() - m.append(TestMiddlewareClassA) - m.append(TestMiddlewareReturning) - m.append(TestMiddlewareClassB) - m.run(a, lambda: a.append('Callback')) - - self.assertEqual(a, ['A']) diff --git a/tests/test_notification.py b/tests/test_notification.py deleted file mode 100644 index 8ef54ffa..00000000 --- a/tests/test_notification.py +++ /dev/null @@ -1,158 +0,0 @@ -import inspect -import json -import sys -import unittest - -from bugsnag.configuration import Configuration -from bugsnag.notification import Notification -from tests import fixtures - -try: - reload # Python 2.x -except NameError: - try: - from importlib import reload # Python 3.4+ - except ImportError: - from imp import reload # Python 3.0 - 3.3 - - -class TestNotification(unittest.TestCase): - - def test_sanitize(self): - """ - It should sanitize request data - """ - config = Configuration() - notification = Notification(Exception("oops"), config, {}, - request={"params": {"password": "secret"}}) - - notification.add_tab("request", {"arguments": {"password": "secret"}}) - - payload = json.loads(notification._payload()) - request = payload['events'][0]['metaData']['request'] - self.assertEqual(request['arguments']['password'], '[FILTERED]') - self.assertEqual(request['params']['password'], '[FILTERED]') - - def test_code(self): - """ - It should include code - """ - config = Configuration() - line = inspect.currentframe().f_lineno + 1 - notification = Notification(Exception("oops"), config, {}) - - payload = json.loads(notification._payload()) - - code = payload['events'][0]['exceptions'][0]['stacktrace'][0]['code'] - lvl = " " - self.assertEqual(code[str(line - 3)], lvl + "\"\"\"") - self.assertEqual(code[str(line - 2)], lvl + "config = Configuration()") - self.assertEqual(code[str(line - 1)], - lvl + "line = inspect.currentframe().f_lineno + 1") - self.assertEqual( - code[str(line)], - lvl + - "notification = Notification(Exception(\"oops\"), config, {})" - ) - self.assertEqual(code[str(line + 1)], "") - self.assertEqual(code[str(line + 2)], - lvl + "payload = json.loads(notification._payload())") - self.assertEqual(code[str(line + 3)], "") - - def test_code_at_start_of_file(self): - - config = Configuration() - notification = Notification(fixtures.start_of_file[1], config, {}, - traceback=fixtures.start_of_file[2]) - - payload = json.loads(notification._payload()) - - code = payload['events'][0]['exceptions'][0]['stacktrace'][0]['code'] - self.assertEqual( - {'1': '# flake8: noqa', - '2': 'try:', - '3': ' import sys; raise Exception("start")', - '4': 'except Exception: start_of_file = sys.exc_info()', - '5': '# 4', - '6': '# 5', - '7': '# 6'}, code) - - def test_code_at_end_of_file(self): - - config = Configuration() - notification = Notification(fixtures.end_of_file[1], config, {}, - traceback=fixtures.end_of_file[2]) - - payload = json.loads(notification._payload()) - - code = payload['events'][0]['exceptions'][0]['stacktrace'][0]['code'] - self.assertEqual( - {'6': '# 5', - '7': '# 6', - '8': '# 7', - '9': '# 8', - '10': 'try:', - '11': ' import sys; raise Exception("end")', - '12': 'except Exception: end_of_file = sys.exc_info()'}, code) - - def test_code_turned_off(self): - config = Configuration() - config.send_code = False - notification = Notification(Exception("oops"), config, {}, - traceback=fixtures.end_of_file[2]) - - payload = json.loads(notification._payload()) - - code = payload['events'][0]['exceptions'][0]['stacktrace'][0]['code'] - self.assertEqual(code, None) - - def test_no_traceback_exclude_modules(self): - from tests.fixtures import helpers - config = Configuration() - - notification = helpers.invoke_exception_on_other_file(config) - - payload = json.loads(notification._payload()) - exception = payload['events'][0]['exceptions'][0] - first_traceback = exception['stacktrace'][0] - - self.assertEqual(first_traceback['file'], 'fixtures/helpers.py') - - def test_traceback_exclude_modules(self): - # Make sure samples.py is compiling to pyc - import py_compile - py_compile.compile('./fixtures/helpers.py') - - from tests.fixtures import helpers - reload(helpers) # .py variation might be loaded from previous test. - - if sys.version_info < (3, 0): - # Python 2.6 & 2.7 returns the cached file on __file__, - # and hence we verify it returns .pyc for these versions - # and the code at _generate_stacktrace() handles that. - self.assertTrue(helpers.__file__.endswith('.pyc')) - - config = Configuration() - config.traceback_exclude_modules = [helpers] - - notification = helpers.invoke_exception_on_other_file(config) - - payload = json.loads(notification._payload()) - exception = payload['events'][0]['exceptions'][0] - first_traceback = exception['stacktrace'][0] - self.assertEqual(first_traceback['file'], 'test_notification.py') - - def test_device_data(self): - """ - It should include device data - """ - config = Configuration() - config.hostname = 'test_host_name' - config.runtime_versions = {'python': '9.9.9'} - notification = Notification(Exception("oops"), config, {}) - - payload = json.loads(notification._payload()) - - device = payload['events'][0]['device'] - self.assertEqual('test_host_name', device['hostname']) - self.assertEqual('9.9.9', device['runtimeVersions']['python']) diff --git a/tests/test_notify.py b/tests/test_notify.py deleted file mode 100644 index f787f519..00000000 --- a/tests/test_notify.py +++ /dev/null @@ -1,717 +0,0 @@ -# -*- coding: utf8 -*- - -import sys -import time - -from six import u - -import bugsnag -from tests.utils import ScaryException, IntegrationTest -from tests.fixtures import samples - - -class TestBugsnag(IntegrationTest): - - def setUp(self): - super(TestBugsnag, self).setUp() - bugsnag.configure(use_ssl=False, - endpoint=self.server.address, - api_key='tomatoes', - notify_release_stages=['dev'], - release_stage='dev', - asynchronous=False) - - def test_asynchronous_notify(self): - bugsnag.configure(asynchronous=True) - self.server.paused = True - bugsnag.notify(ScaryException('unexpected failover')) - self.server.paused = False - - start = time.time() - while len(self.server.received) == 0: - if time.time() > (start + 0.5): - raise Exception( - 'Timed out while waiting for asynchronous request.') - - time.sleep(0.001) - - self.assertEqual(len(self.server.received), 1) - - def test_notify_method(self): - bugsnag.notify(ScaryException('unexpected failover')) - request = self.server.received[0] - self.assertEqual('POST', request['method']) - - def test_notify_request_count(self): - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(1, len(self.server.received)) - - def test_notify_configured_api_key(self): - bugsnag.notify(ScaryException('unexpected failover')) - headers = self.server.received[0]['headers'] - self.assertEqual('tomatoes', headers['Bugsnag-Api-Key']) - - def test_notify_configured_release_stage(self): - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('dev', event['releaseStage']) - - def test_notify_unconfigured_release_stage(self): - bugsnag.configure(release_stage=['pickles']) - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(0, len(self.server.received)) - - def test_notify_default_severity(self): - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('warning', event['severity']) - - def test_notify_override_severity(self): - bugsnag.notify(ScaryException('unexpected failover'), - severity='info') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('info', event['severity']) - - def test_notify_configured_app_version(self): - bugsnag.configure(app_version='343.2.10') - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('343.2.10', event['appVersion']) - - def test_notify_override_context(self): - bugsnag.notify(ScaryException('unexpected failover'), - context='/some/path') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('/some/path', event['context']) - - def test_notify_override_grouping_hash(self): - bugsnag.notify(ScaryException('unexpected failover'), - grouping_hash='Callout errors') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('Callout errors', event['groupingHash']) - - def test_notify_override_user(self): - bugsnag.notify(ScaryException('unexpected failover'), - user={'name': 'bob', - 'email': 'mcbob@example.com', - 'id': '542347329'}) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('bob', event['user']['name']) - self.assertEqual('542347329', event['user']['id']) - self.assertEqual('mcbob@example.com', event['user']['email']) - - def test_notify_configured_hostname(self): - bugsnag.configure(hostname='I_AM_ROOT') - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('I_AM_ROOT', event['device']['hostname']) - - def test_notify_override_metadata_sections(self): - bugsnag.add_metadata_tab('food', {'beans': 3, 'corn': 'purple'}) - bugsnag.notify(ScaryException('unexpected failover'), - meta_data={'food': {'beans': 5}, - 'skills': {'spear': 6}}) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(6, event['metaData']['skills']['spear']) - self.assertEqual('purple', event['metaData']['food']['corn']) - self.assertEqual(5, event['metaData']['food']['beans']) - - def test_notify_configured_metadata_sections(self): - bugsnag.add_metadata_tab('food', {'beans': 3, 'corn': 'purple'}) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('purple', event['metaData']['food']['corn']) - self.assertEqual(3, event['metaData']['food']['beans']) - - def test_notify_metadata_filter(self): - bugsnag.configure(params_filters=['apple', 'grape']) - bugsnag.notify(ScaryException('unexpected failover'), - apple='four', cantaloupe='green') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('[FILTERED]', event['metaData']['custom']['apple']) - self.assertEqual('green', event['metaData']['custom']['cantaloupe']) - - def test_notify_device_filter(self): - bugsnag.configure(params_filters=['hostname']) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('[FILTERED]', event['device']['hostname']) - - def test_notify_user_filter(self): - bugsnag.configure(params_filters=['address', 'phonenumber']) - bugsnag.notify(ScaryException('unexpected failover'), - user={ - "id": "test-man", - "address": "123 street\n cooltown\n ABC 123", - "phonenumber": "12345678900", - "firstname": "Test", - "lastname": "Man" - }) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('[FILTERED]', event['user']['address']) - self.assertEqual('[FILTERED]', event['user']['phonenumber']) - self.assertEqual('test-man', event['user']['id']) - self.assertEqual('Test', event['user']['firstname']) - self.assertEqual('Man', event['user']['lastname']) - - def test_notify_payload_matching_filter(self): - bugsnag.configure(params_filters=['number']) - bugsnag.notify(ScaryException('unexpected failover'), - apple='four', number=76) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('four', event['metaData']['custom']['apple']) - self.assertEqual('[FILTERED]', event['metaData']['custom']['number']) - self.assertEqual(173, exception['stacktrace'][0]['lineNumber']) - - def test_notify_ignore_class(self): - bugsnag.configure(ignore_classes=['tests.utils.ScaryException']) - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(0, len(self.server.received)) - - def test_notify_configured_invalid_api_key(self): - bugsnag.configure(api_key=None) - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(0, len(self.server.received)) - - def test_notify_sends_when_before_notify_throws(self): - - def callback(report): - report.add_custom_data('foo', 'bar') - raise ScaryException('oh no') - - bugsnag.before_notify(callback) - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(1, len(self.server.received)) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('bar', event['metaData']['custom']['foo']) - - def test_notify_before_notify_remove_api_key(self): - - def callback(report): - report.api_key = None - - bugsnag.before_notify(callback) - bugsnag.notify(ScaryException('unexpected failover')) - self.assertEqual(0, len(self.server.received)) - - def test_notify_before_notify_modifying_api_key(self): - - def callback(report): - report.api_key = 'sandwich' - - bugsnag.before_notify(callback) - bugsnag.notify(ScaryException('unexpected failover')) - headers = self.server.received[0]['headers'] - self.assertEqual('sandwich', headers['Bugsnag-Api-Key']) - - def test_notify_before_notify_modifying_metadata(self): - - def callback(report): - report.meta_data['foo'] = {'sandwich': 'bar'} - - bugsnag.before_notify(callback) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('bar', event['metaData']['foo']['sandwich']) - - def test_notify_before_notify_add_custom_data(self): - - def callback(report): - report.add_custom_data('color', 'green') - - bugsnag.before_notify(callback) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('green', event['metaData']['custom']['color']) - - def test_notify_configured_lib_root(self): - bugsnag.configure(lib_root='/the/basement') - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('/the/basement', event['libRoot']) - - def test_notify_configured_project_root(self): - bugsnag.configure(project_root='/the/basement') - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('/the/basement', event['projectRoot']) - - def test_notify_invalid_severity(self): - bugsnag.notify(ScaryException('unexpected failover'), - severity='debug') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('warning', event['severity']) - - def test_notify_override_deprecated_user_id(self): - bugsnag.notify(ScaryException('unexpected failover'), - user_id='542347329') - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('542347329', event['user']['id']) - - def test_notify_override_api_key(self): - bugsnag.notify(ScaryException('unexpected failover'), - api_key='gravy!') - headers = self.server.received[0]['headers'] - self.assertEqual('gravy!', headers['Bugsnag-Api-Key']) - - def test_notify_payload_version(self): - bugsnag.notify(ScaryException('unexpected failover')) - headers = self.server.received[0]['headers'] - self.assertEqual('4.0', headers['Bugsnag-Payload-Version']) - - def test_notify_error_class(self): - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('tests.utils.ScaryException', exception['errorClass']) - - def test_notify_bad_encoding_metadata(self): - - class BadThings: - - def __str__(self): - raise Exception('nah') - - bugsnag.notify(ScaryException('unexpected failover'), bad=BadThings()) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('[BADENCODING]', event['metaData']['custom']['bad']) - - def test_notify_recursive_metadata_dict(self): - a = {'foo': 'bar'} - a['baz'] = a - bugsnag.add_metadata_tab('a', a) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('bar', event['metaData']['a']['foo']) - self.assertEqual('[RECURSIVE]', event['metaData']['a']['baz']['baz']) - - def test_notify_recursive_metadata_array(self): - a = ['foo', 'bar'] - a.append(a) - bugsnag.add_metadata_tab('a', {'b': a}) - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(['foo', 'bar', '[RECURSIVE]'], - event['metaData']['a']['b']) - - def test_notify_metadata_bool_value(self): - bugsnag.notify(ScaryException('unexpected failover'), - value=True, value2=False) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(True, event['metaData']['custom']['value']) - self.assertEqual(False, event['metaData']['custom']['value2']) - - def test_notify_metadata_complex_value(self): - bugsnag.notify(ScaryException('unexpected failover'), - value=(5 + 0j), value2=(13 + 3.4j)) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual('(5+0j)', event['metaData']['custom']['value']) - self.assertEqual('(13+3.4j)', event['metaData']['custom']['value2']) - - def test_notify_non_exception(self): - bugsnag.notify(2) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual(1, len(self.server.received)) - self.assertEqual('RuntimeError', exception['errorClass']) - self.assertTrue(repr(2) in exception['message']) - - def test_notify_bad_encoding_exception_tuple(self): - - class BadThings: - - def __repr__(self): - raise Exception('nah') - - bugsnag.notify(BadThings()) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual('[BADENCODING]', exception['message']) - - def test_notify_single_value_tuple(self): - bugsnag.notify((None,)) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual(1, len(self.server.received)) - self.assertEqual('RuntimeError', exception['errorClass']) - self.assertTrue(repr(None) in exception['message']) - - def test_notify_invalid_values_tuple(self): - bugsnag.notify((None, 2, "foo")) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual(1, len(self.server.received)) - self.assertEqual('RuntimeError', exception['errorClass']) - self.assertTrue(repr(2) in exception['message']) - - def test_notify_exception_with_traceback_option(self): - backtrace = None - try: - raise ScaryException('foo') - except ScaryException: - backtrace = sys.exc_info()[2] - - bugsnag.notify(Exception("foo"), traceback=backtrace) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - stacktrace = exception['stacktrace'] - self.assertEqual(1, len(self.server.received)) - self.assertEqual('foo', exception['message']) - self.assertEqual('test_notify_exception_with_traceback_option', - stacktrace[0]['method']) - - def test_notify_exception_tuple_with_traceback(self): - - def send_notify(): - backtrace = None - try: - raise ScaryException('foo') - except ScaryException: - backtrace = sys.exc_info()[2] - bugsnag.notify((Exception, Exception("foo"), backtrace)) - - send_notify() - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - stacktrace = exception['stacktrace'] - self.assertEqual(1, len(self.server.received)) - self.assertEqual('foo', exception['message']) - self.assertEqual('send_notify', - stacktrace[0]['method']) - - def test_notify_exception_tuple(self): - bugsnag.notify((Exception, Exception("foo"), None)) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual(1, len(self.server.received)) - self.assertEqual(u("RuntimeError"), exception['errorClass']) - - def test_notify_metadata_set_value(self): - bugsnag.notify(ScaryException('unexpected failover'), - value=set([6, "cow", "gravy"])) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - value = event['metaData']['custom']['value'] - self.assertEqual(3, len(value)) - self.assertTrue(6 in value) - self.assertTrue("cow" in value) - self.assertTrue("gravy" in value) - - def test_notify_metadata_tuple_value(self): - bugsnag.notify(ScaryException('unexpected failover'), - value=(3, "cow", "gravy")) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual([3, "cow", "gravy"], - event['metaData']['custom']['value']) - - def test_notify_metadata_integer_value(self): - bugsnag.notify(ScaryException('unexpected failover'), - value=5, value2=-13) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(5, event['metaData']['custom']['value']) - self.assertEqual(-13, event['metaData']['custom']['value2']) - - def test_notify_error_message(self): - bugsnag.notify(ScaryException(u('unexpécted failover'))) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - exception = event['exceptions'][0] - self.assertEqual(u('unexpécted failover'), exception['message']) - - def test_notify_unicode_metadata(self): - bins = (u('\x98\x00\x00\x00\t\x81\x19\x1b\x00\x00\x00\x00\xd4\x07\x00' - '\x00\x00\x00\x00\x00R\x00\x00\x00\x00\x00\xff\xff\xff\xffe' - '\x00\x00\x00\x02project\x00%\x00\x00\x00f65f051b-d762-5983' - '-838b-a05aadc06a5\x00\x02uid\x00%\x00\x00\x001bab969f-7b30' - '-459a-adee-917b9e028eed\x00\x00')) - self_class = 'tests.test_notify.TestBugsnag' - bugsnag.notify(Exception('free food'), meta_data={'payload': { - 'project': u('∆πåß∂ƒ'), - 'filename': u('DISPOSITIFS DE SÉCURITÉ.pdf'), - u('♥♥i'): u('♥♥♥♥♥♥'), - 'src_name': u('☻☻☻☻☻ RDC DISPOSITIFS DE SÉCURTÉ.pdf'), - u('accénted'): u('☘☘☘éééé@me.com'), - 'class': self.__class__, - 'another_class': dict, - 'self': self, - 'var': bins - }}) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertEqual(u('∆πåß∂ƒ'), event['metaData']['payload']['project']) - self.assertEqual(u('♥♥♥♥♥♥'), - event['metaData']['payload'][u('♥♥i')]) - self.assertEqual(u('DISPOSITIFS DE SÉCURITÉ.pdf'), - event['metaData']['payload']['filename']) - self.assertEqual(u('☻☻☻☻☻ RDC DISPOSITIFS DE SÉCURTÉ.pdf'), - event['metaData']['payload']['src_name']) - self.assertEqual(u('☘☘☘éééé@me.com'), - event['metaData']['payload'][u('accénted')]) - self.assertEqual('test_notify_unicode_metadata (%s)' % self_class, - event['metaData']['payload']['self']) - self.assertEqual(bins, event['metaData']['payload']['var']) - self.assertEqual("", - event['metaData']['payload']['class']) - - def test_notify_stacktrace(self): - samples.call_bugsnag_nested() - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - frames = event['exceptions'][0]['stacktrace'] - - self.assertTrue(frames[0]['file'].endswith('fixtures/samples.py')) - self.assertEqual(11, frames[0]['lineNumber']) - self.assertEqual('chain_3', frames[0]['method']) - self.assertEqual('chain_3', frames[0]['method']) - self.assertEqual('', frames[0]['code']['7']) - self.assertEqual('', frames[0]['code']['8']) - self.assertEqual('def chain_3():', frames[0]['code']['9']) - self.assertEqual(' import bugsnag', frames[0]['code']['10']) - self.assertEqual(" bugsnag.notify(Exception('oh noooo'))", - frames[0]['code']['11']) - - self.assertTrue(frames[1]['file'].endswith('fixtures/samples.py')) - self.assertEqual(6, frames[1]['lineNumber']) - self.assertEqual('chain_2', frames[1]['method']) - self.assertEqual('', frames[1]['code']['4']) - self.assertEqual('def chain_2():', frames[1]['code']['5']) - self.assertEqual(' chain_3()', frames[1]['code']['6']) - self.assertEqual('', frames[1]['code']['7']) - self.assertEqual('', frames[1]['code']['8']) - - self.assertTrue(frames[2]['file'].endswith('fixtures/samples.py')) - self.assertEqual(2, frames[2]['lineNumber']) - self.assertEqual('def call_bugsnag_nested():', frames[2]['code']['1']) - self.assertEqual(' chain_2()', frames[2]['code']['2']) - self.assertEqual('', frames[2]['code']['3']) - self.assertEqual('', frames[2]['code']['4']) - - def test_notify_proxy(self): - bugsnag.configure(proxy_host=self.server.url) - bugsnag.notify(ScaryException('unexpected failover')) - - self.assertEqual(len(self.server.received), 1) - self.assertEqual(self.server.received[0]['method'], 'POST') - self.assertEqual(self.server.received[0]['path'].strip('/'), - self.server.url) - - def test_notify_unhandled_defaults(self): - bugsnag.notify(ScaryException("unexpected failover")) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertFalse(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "handledException" - }) - - def test_notify_severity_overridden(self): - bugsnag.notify(ScaryException("unexpected failover"), severity="info") - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertFalse(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "userSpecifiedSeverity" - }) - - def test_notify_unhandled_severity_callback(self): - def callback(report): - report.severity = "info" - - bugsnag.before_notify(callback) - - bugsnag.notify(ScaryException("unexpected failover"), severity="error") - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertFalse(event['unhandled']) - self.assertEqual(event['severityReason'], { - "type": "userCallbackSetSeverity" - }) - - def test_middleware_stack_order_legacy(self): - def first_callback(notification): - notification.meta_data['test']['array'].append(1) - - def second_callback(notification): - notification.meta_data['test']['array'].append(2) - - # Add a regular callback function - bugsnag.before_notify(second_callback) - - # Simulate an internal middleware - bugsnag.legacy.configuration.internal_middleware.before_notify( - first_callback) - - bugsnag.notify(ScaryException('unexpected failover'), - test={'array': []}) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['metaData']['test']['array'], [1, 2]) - - def test_middleware_stack_order(self): - client = bugsnag.Client(use_ssl=False, - endpoint=self.server.address, - api_key='tomatoes', - notify_release_stages=['dev'], - release_stage='dev', - asynchronous=False) - - def first_callback(notification): - notification.meta_data['test']['array'].append(1) - - def second_callback(notification): - notification.meta_data['test']['array'].append(2) - - client.configuration.middleware.before_notify(second_callback) - client.configuration.internal_middleware.before_notify(first_callback) - - client.notify(ScaryException('unexpected failover'), - test={'array': []}) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['metaData']['test']['array'], [1, 2]) - - def test_internal_middleware_changes_severity(self): - client = bugsnag.Client(use_ssl=False, - endpoint=self.server.address, - api_key='tomatoes', - notify_release_stages=['dev'], - release_stage='dev', - asynchronous=False) - - def severity_callback(notification): - notification.severity = 'info' - - internal_middleware = client.configuration.internal_middleware - internal_middleware.before_notify(severity_callback) - - client.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['severity'], 'info') - self.assertEqual(event['severityReason']['type'], 'handledException') - - def test_internal_middleware_can_change_severity_reason(self): - client = bugsnag.Client(use_ssl=False, - endpoint=self.server.address, - api_key='tomatoes', - notify_release_stages=['dev'], - release_stage='dev', - asynchronous=False) - - def severity_reason_callback(notification): - notification.severity_reason['type'] = 'testReason' - - internal_middleware = client.configuration.internal_middleware - internal_middleware.before_notify(severity_reason_callback) - - client.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['severityReason']['type'], 'testReason') - - def test_external_middleware_can_change_severity(self): - - def severity_callback(notification): - notification.severity = 'info' - - bugsnag.before_notify(severity_callback) - - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['severity'], 'info') - self.assertEqual(event['severityReason']['type'], - 'userCallbackSetSeverity') - - def test_external_middleware_cannot_change_severity_reason(self): - - def severity_reason_callback(notification): - notification.severity_reason['type'] = 'testReason' - - bugsnag.before_notify(severity_reason_callback) - - bugsnag.notify(ScaryException('unexpected failover')) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - - self.assertEqual(event['severityReason']['type'], 'handledException') - - def test_auto_notify_defaults(self): - bugsnag.auto_notify(ScaryException("unexpected failover")) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertTrue(event['unhandled']) - self.assertEqual(event['severity'], 'error') - self.assertEqual(event['severityReason'], { - "type": "unhandledException" - }) - - def test_auto_notify_overrides(self): - bugsnag.auto_notify( - ScaryException("unexpected failover"), - severity='info', - unhandled=False, - severity_reason={ - "type": "middleware_handler", - "attributes": { - "name": "test middleware" - } - } - ) - - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertFalse(event['unhandled']) - self.assertEqual(event['severity'], 'info') - self.assertEqual(event['severityReason'], { - "type": "middleware_handler", - "attributes": { - "name": "test middleware" - } - }) diff --git a/tests/test_sessiontracker.py b/tests/test_sessiontracker.py deleted file mode 100644 index a191979f..00000000 --- a/tests/test_sessiontracker.py +++ /dev/null @@ -1,124 +0,0 @@ -import platform -import time - -from bugsnag import Client -from bugsnag.notification import Notification -from bugsnag.configuration import Configuration -from bugsnag.sessiontracker import SessionTracker -from bugsnag.utils import ThreadLocals, package_version -from tests.utils import IntegrationTest - - -class TestConfiguration(IntegrationTest): - def setUp(self): - super(TestConfiguration, self).setUp() - self.config = Configuration() - self.config.auto_capture_sessions = True - - def test_session_tracker_adds_session_object_to_queue(self): - tracker = SessionTracker(self.config) - tracker.auto_sessions = True - tracker.start_session() - self.assertEqual(len(tracker.session_counts), 1) - for key, value in tracker.session_counts.items(): - self.assertEqual(value, 1) - - def test_session_tracker_stores_session_in_threadlocals(self): - locs = ThreadLocals.get_instance() - tracker = SessionTracker(self.config) - tracker.auto_sessions = True - tracker.start_session() - session = locs.get_item('bugsnag-session') - self.assertTrue('id' in session) - self.assertTrue('startedAt' in session) - self.assertTrue('events' in session) - self.assertTrue('handled' in session['events']) - self.assertTrue('unhandled' in session['events']) - self.assertEqual(session['events']['handled'], 0) - self.assertEqual(session['events']['unhandled'], 0) - - def test_session_tracker_sessions_are_unique(self): - tracker = SessionTracker(self.config) - tracker.auto_sessions = True - locs = ThreadLocals.get_instance() - tracker.start_session() - session_one = locs.get_item('bugsnag-session').copy() - tracker.start_session() - session_two = locs.get_item('bugsnag-session').copy() - self.assertNotEqual(session_one['id'], session_two['id']) - - def test_session_tracker_send_sessions_sends_sessions(self): - client = Client( - auto_capture_sessions=True, - session_endpoint=self.server.url, - asynchronous=False - ) - client.session_tracker.start_session() - self.assertEqual(len(client.session_tracker.session_counts), 1) - client.session_tracker.send_sessions() - self.assertEqual(len(client.session_tracker.session_counts), 0) - json_body = self.server.received[0]['json_body'] - self.assertTrue('app' in json_body) - self.assertTrue('notifier' in json_body) - self.assertTrue('device' in json_body) - self.assertTrue('sessionCounts' in json_body) - self.assertEqual(len(json_body['sessionCounts']), 1) - - def test_session_tracker_sets_details_from_config(self): - client = Client( - auto_capture_sessions=True, - session_endpoint=self.server.url, - asynchronous=False - ) - client.session_tracker.start_session() - client.session_tracker.send_sessions() - json_body = self.server.received[0]['json_body'] - # Notifier properties - notifier = json_body['notifier'] - self.assertTrue('name' in notifier) - self.assertEqual(notifier['name'], Notification.NOTIFIER_NAME) - self.assertTrue('url' in notifier) - self.assertEqual(notifier['url'], Notification.NOTIFIER_URL) - self.assertTrue('version' in notifier) - notifier_version = package_version('bugsnag') or 'unknown' - self.assertEqual(notifier['version'], notifier_version) - # App properties - app = json_body['app'] - self.assertTrue('releaseStage' in app) - self.assertEqual(app['releaseStage'], - client.configuration.get('release_stage')) - self.assertTrue('version' in app) - self.assertEqual(app['version'], - client.configuration.get('app_version')) - # Device properties - device = json_body['device'] - self.assertTrue('hostname' in device) - self.assertEqual(device['hostname'], - client.configuration.get('hostname')) - self.assertTrue('runtimeVersions' in device) - self.assertEqual(device['runtimeVersions']['python'], - platform.python_version()) - - def test_session_middleware_attaches_session_to_notification(self): - client = Client( - auto_capture_sessions=True, - session_endpoint=self.server.url + '/ignore', - endpoint=self.server.url, - asynchronous=False - ) - client.session_tracker.start_session() - client.notify(Exception("Test")) - while len(self.server.received) == 0: - time.sleep(0.5) - json_body = self.server.received[0]['json_body'] - event = json_body['events'][0] - self.assertTrue('session' in event) - session = event['session'] - self.assertTrue('id' in session) - self.assertTrue('startedAt' in session) - self.assertTrue('events' in session) - sesevents = session['events'] - self.assertTrue('unhandled' in sesevents) - self.assertEqual(sesevents['unhandled'], 0) - self.assertTrue('handled' in sesevents) - self.assertEqual(sesevents['handled'], 1) diff --git a/tests/test_utils.py b/tests/test_utils.py index a53a5c17..ccb698d5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,207 +11,33 @@ class TestUtils(unittest.TestCase): - def test_encode_filters(self): - data = FilterDict({"credit_card": "123213213123", "password": "456", - "cake": True}) - encoder = SanitizingJSONEncoder(keyword_filters=["credit_card", - "password"]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, {"credit_card": "[FILTERED]", - "password": "[FILTERED]", - "cake": True}) - - def test_sanitize_list(self): - data = FilterDict({"list": ["carrots", "apples", "peas"], - "passwords": ["abc", "def"]}) - encoder = SanitizingJSONEncoder(keyword_filters=["credit_card", - "passwords"]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, {"list": ["carrots", "apples", "peas"], - "passwords": "[FILTERED]"}) - - def test_sanitize_valid_unicode_object(self): - data = {"item": u('\U0001f62c')} - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, data) - - def test_sanitize_nested_object_filters(self): - data = FilterDict({"metadata": {"another_password": "My password"}}) + def test_filter_dict_with_inner_dict(self): + data = { + "level1-key1": { + "level2-key1": FilterDict({ + "level3-key1": { + 'level4-key1': 'level4-value1'} + }), + "level2-key2": FilterDict({ + "level3-key2": "level3-value1", + "level3-key3": { + 'level4-key2': 'level4-value2'} + }), + } + } encoder = SanitizingJSONEncoder(keyword_filters=["password"]) sane_data = json.loads(encoder.encode(data)) self.assertEqual(sane_data, - {"metadata": {"another_password": "[FILTERED]"}}) - - def test_sanitize_bad_utf8_object(self): - data = {"bad_utf8": u("test \xe9")} - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, data) - - def test_sanitize_unencoded_object(self): - data = {"exc": Exception()} - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, {"exc": ""}) - - def test_json_encode(self): - payload = {"a": u("a") * 512 * 1024} - expected = {"a": u("a") * 1024} - encoder = SanitizingJSONEncoder(keyword_filters=[]) - self.assertEqual(json.loads(encoder.encode(payload)), expected) - - def test_filter_dict(self): - data = FilterDict({"metadata": {"another_password": "My password"}}) - encoder = SanitizingJSONEncoder(keyword_filters=["password"]) - sane_data = encoder.filter_string_values(data) - self.assertEqual(sane_data, - {"metadata": {"another_password": "[FILTERED]"}}) - - def test_unfiltered_encode(self): - data = {"metadata": {"another_password": "My password"}} - encoder = SanitizingJSONEncoder(keyword_filters=["password"]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, data) - - def test_thread_locals(self): - key = "TEST_THREAD_LOCALS" - val = {"Test": "Thread", "Locals": "Here"} - locs = ThreadLocals.get_instance() - self.assertFalse(locs.has_item(key)) - locs.set_item(key, val) - self.assertTrue(locs.has_item(key)) - item = locs.get_item(key) - self.assertEqual(item, val) - locs.del_item(key) - self.assertFalse(locs.has_item(key)) - item = locs.get_item(key, "default") - self.assertEqual(item, "default") - - def test_encoding_recursive(self): - """ - Test that recursive data structures are replaced with '[RECURSIVE]' - """ - data = {"Test": ["a", "b", "c"]} - data["Self"] = data - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, - {"Test": ["a", "b", "c"], "Self": "[RECURSIVE]"}) - - def test_encoding_recursive_repeated(self): - """ - Test that encoding the same object twice produces the same result - """ - data = {"Test": ["a", "b", "c"]} - data["Self"] = data - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, - {"Test": ["a", "b", "c"], "Self": "[RECURSIVE]"}) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, - {"Test": ["a", "b", "c"], "Self": "[RECURSIVE]"}) - - def test_encoding_nested_repeated(self): - """ - Test that encoding the same object within a new object is not - incorrectly marked as recursive - """ - encoder = SanitizingJSONEncoder(keyword_filters=[]) - data = {"Test": ["a", "b", "c"]} - encoder.encode(data) - data = {"Previous": data, "Other": 400} - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, - {"Other": 400, - "Previous": {"Test": ["a", "b", "c"]}}) - - def test_encoding_oversized_recursive(self): - """ - Test that encoding an object which requires trimming clips recursion - correctly - """ - data = {"Test": ["a" * 128 * 1024, "b", "c"], "Other": {"a": 300}} - data["Self"] = data - encoder = SanitizingJSONEncoder(keyword_filters=[]) - sane_data = json.loads(encoder.encode(data)) - self.assertEqual(sane_data, - {"Test": ["a" * 1024, "b", "c"], - "Self": "[RECURSIVE]", - "Other": {"a": 300}}) - - def test_encoding_time(self): - """ - Test that encoding a large object is sufficiently speedy - """ - setup = """\ -import json -from tests.large_object import large_object_file_path -from bugsnag.utils import SanitizingJSONEncoder -encoder = SanitizingJSONEncoder(keyword_filters=[]) -with open(large_object_file_path()) as json_data: - data = json.load(json_data) - """ - stmt = """\ -encoder.encode(data) - """ - time = timeit.timeit(stmt=stmt, setup=setup, number=1000) - maximum_time = 6 - if sys.version_info[0:2] <= (2, 6): - # json encoding is very slow on python 2.6 so we need to increase - # the allowable time when running on it - maximum_time = 18 - self.assertTrue(time < maximum_time, - "Encoding required {0}s (expected {1}s)".format( - time, maximum_time - )) - - def test_filter_string_values_list_handling(self): - """ - Test that filter_string_values can accept a list for the ignored - parameter for backwards compatibility - """ - data = {} - encoder = SanitizingJSONEncoder() - # no assert as we are just expecting this not to throw - encoder.filter_string_values(data, ['password']) - - def test_sanitize_list_handling(self): - """ - Test that _sanitize can accept a list for the ignored parameter for - backwards compatibility - """ - data = {} - encoder = SanitizingJSONEncoder() - # no assert as we are just expecting this not to throw - encoder._sanitize(data, ['password'], ['password']) - - def test_json_encode_invalid_keys(self): - """ - Test that _sanitize can accept some invalid json where a function - name or some other bad data is passed as a key in the payload - dictionary. - """ - encoder = SanitizingJSONEncoder(keyword_filters=[]) - - def foo(): - return "123" - - result = json.loads(encoder.encode({foo: "a"})) - self.assertTrue(re.match(r'