debian-python-sentry/tests/conftest.py

270 lines
7.1 KiB
Python

import os
import subprocess
import json
import pytest
import gevent
import eventlet
import sentry_sdk
from sentry_sdk._compat import reraise, string_types, iteritems
from sentry_sdk.transport import Transport
from tests import _warning_recorder, _warning_recorder_mgr
SEMAPHORE = "./semaphore"
if not os.path.isfile(SEMAPHORE):
SEMAPHORE = None
try:
import pytest_benchmark
except ImportError:
@pytest.fixture
def benchmark():
return lambda x: x()
else:
del pytest_benchmark
@pytest.fixture(autouse=True)
def internal_exceptions(request, monkeypatch):
errors = []
if "tests_internal_exceptions" in request.keywords:
return
def _capture_internal_exception(self, exc_info):
errors.append(exc_info)
@request.addfinalizer
def _():
for e in errors:
reraise(*e)
monkeypatch.setattr(
sentry_sdk.Hub, "_capture_internal_exception", _capture_internal_exception
)
return errors
@pytest.fixture(autouse=True, scope="session")
def _capture_internal_warnings():
yield
_warning_recorder_mgr.__exit__(None, None, None)
recorder = _warning_recorder
for warning in recorder:
try:
if isinstance(warning.message, ResourceWarning):
continue
except NameError:
pass
# pytest-django
if "getfuncargvalue" in str(warning.message):
continue
# Happens when re-initializing the SDK
if "but it was only enabled on init()" in str(warning.message):
continue
# sanic's usage of aiohttp for test client
if "verify_ssl is deprecated, use ssl=False instead" in str(warning.message):
continue
if "getargspec" in str(warning.message) and warning.filename.endswith(
("pyramid/config/util.py", "pyramid/config/views.py")
):
continue
if "isAlive() is deprecated" in str(
warning.message
) and warning.filename.endswith("celery/utils/timer2.py"):
continue
if "collections.abc" in str(warning.message) and warning.filename.endswith(
("celery/canvas.py", "werkzeug/datastructures.py", "tornado/httputil.py")
):
continue
# Django 1.7 emits a (seemingly) false-positive warning for our test
# app and suggests to use a middleware that does not exist in later
# Django versions.
if "SessionAuthenticationMiddleware" in str(warning.message):
continue
if "Something has already installed a non-asyncio" in str(warning.message):
continue
if "dns.hash" in str(warning.message) or "dns/namedict" in warning.filename:
continue
raise AssertionError(warning)
@pytest.fixture
def monkeypatch_test_transport(monkeypatch, semaphore_normalize):
def check_event(event):
def check_string_keys(map):
for key, value in iteritems(map):
assert isinstance(key, string_types)
if isinstance(value, dict):
check_string_keys(value)
check_string_keys(event)
semaphore_normalize(event)
def inner(client):
monkeypatch.setattr(client, "transport", TestTransport(check_event))
return inner
def _no_errors_in_semaphore_response(obj):
"""Assert that semaphore didn't throw any errors when processing the
event."""
def inner(obj):
if not isinstance(obj, dict):
return
assert "err" not in obj
for value in obj.values():
inner(value)
try:
inner(obj.get("_meta"))
inner(obj.get(""))
except AssertionError:
raise AssertionError(obj)
@pytest.fixture
def semaphore_normalize(tmpdir):
def inner(event):
if not SEMAPHORE:
return
# Disable subprocess integration
with sentry_sdk.Hub(None):
# not dealing with the subprocess API right now
file = tmpdir.join("event")
file.write(json.dumps(dict(event)))
output = json.loads(
subprocess.check_output(
[SEMAPHORE, "process-event"], stdin=file.open()
).decode("utf-8")
)
_no_errors_in_semaphore_response(output)
output.pop("_meta", None)
return output
return inner
@pytest.fixture
def sentry_init(monkeypatch_test_transport):
def inner(*a, **kw):
hub = sentry_sdk.Hub.current
client = sentry_sdk.Client(*a, **kw)
hub.bind_client(client)
monkeypatch_test_transport(sentry_sdk.Hub.current.client)
return inner
class TestTransport(Transport):
def __init__(self, capture_event_callback):
Transport.__init__(self)
self.capture_event = capture_event_callback
self._queue = None
@pytest.fixture
def capture_events(monkeypatch):
def inner():
events = []
test_client = sentry_sdk.Hub.current.client
old_capture_event = test_client.transport.capture_event
def append(event):
events.append(event)
return old_capture_event(event)
monkeypatch.setattr(test_client.transport, "capture_event", append)
return events
return inner
@pytest.fixture
def capture_events_forksafe(monkeypatch):
def inner():
events_r, events_w = os.pipe()
events_r = os.fdopen(events_r, "rb", 0)
events_w = os.fdopen(events_w, "wb", 0)
test_client = sentry_sdk.Hub.current.client
old_capture_event = test_client.transport.capture_event
def append(event):
events_w.write(json.dumps(event).encode("utf-8"))
events_w.write(b"\n")
return old_capture_event(event)
def flush(timeout=None, callback=None):
events_w.write(b"flush\n")
monkeypatch.setattr(test_client.transport, "capture_event", append)
monkeypatch.setattr(test_client, "flush", flush)
return EventStreamReader(events_r)
return inner
class EventStreamReader(object):
def __init__(self, file):
self.file = file
def read_event(self):
return json.loads(self.file.readline().decode("utf-8"))
def read_flush(self):
assert self.file.readline() == b"flush\n"
# scope=session ensures that fixture is run earlier
@pytest.fixture(scope="session", params=[None, "eventlet", "gevent"])
def maybe_monkeypatched_threading(request):
if request.param == "eventlet":
try:
eventlet.monkey_patch()
except AttributeError as e:
if "'thread.RLock' object has no attribute" in str(e):
# https://bitbucket.org/pypy/pypy/issues/2962/gevent-cannot-patch-rlock-under-pypy-27-7
pytest.skip("https://github.com/eventlet/eventlet/issues/546")
else:
raise
elif request.param == "gevent":
try:
gevent.monkey.patch_all()
except Exception as e:
if "_RLock__owner" in str(e):
pytest.skip("https://github.com/gevent/gevent/issues/1380")
else:
raise
else:
assert request.param is None
return request.param