Skip to content

Commit 5c1da2f

Browse files
committed
Remove forked from test_transport and generalize server to be module level
1 parent 657c2b1 commit 5c1da2f

File tree

3 files changed

+192
-72
lines changed

3 files changed

+192
-72
lines changed

tests/conftest.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
import os
33
import socket
44
import warnings
5+
import brotli
6+
import gzip
7+
import io
58
from threading import Thread
69
from contextlib import contextmanager
710
from http.server import BaseHTTPRequestHandler, HTTPServer
811
from unittest import mock
12+
from collections import namedtuple
913

1014
import pytest
15+
from pytest_localserver.http import WSGIServer
16+
from werkzeug.wrappers import Request, Response
1117
import jsonschema
1218

1319

@@ -23,7 +29,7 @@
2329

2430
import sentry_sdk
2531
import sentry_sdk.utils
26-
from sentry_sdk.envelope import Envelope
32+
from sentry_sdk.envelope import Envelope, parse_json
2733
from sentry_sdk.integrations import ( # noqa: F401
2834
_DEFAULT_INTEGRATIONS,
2935
_installed_integrations,
@@ -663,3 +669,57 @@ def __eq__(self, other):
663669

664670
def __ne__(self, other):
665671
return not self.__eq__(other)
672+
673+
674+
CapturedData = namedtuple("CapturedData", ["path", "event", "envelope", "compressed"])
675+
676+
677+
class CapturingServer(WSGIServer):
678+
def __init__(self, host="127.0.0.1", port=0, ssl_context=None):
679+
WSGIServer.__init__(self, host, port, self, ssl_context=ssl_context)
680+
self.code = 204
681+
self.headers = {}
682+
self.captured = []
683+
684+
def respond_with(self, code=200, headers=None):
685+
self.code = code
686+
if headers:
687+
self.headers = headers
688+
689+
def clear_captured(self):
690+
del self.captured[:]
691+
692+
def __call__(self, environ, start_response):
693+
"""
694+
This is the WSGI application.
695+
"""
696+
request = Request(environ)
697+
event = envelope = None
698+
content_encoding = request.headers.get("content-encoding")
699+
if content_encoding == "gzip":
700+
rdr = gzip.GzipFile(fileobj=io.BytesIO(request.data))
701+
compressed = True
702+
elif content_encoding == "br":
703+
rdr = io.BytesIO(brotli.decompress(request.data))
704+
compressed = True
705+
else:
706+
rdr = io.BytesIO(request.data)
707+
compressed = False
708+
709+
if request.mimetype == "application/json":
710+
event = parse_json(rdr.read())
711+
else:
712+
envelope = Envelope.deserialize_from(rdr)
713+
714+
self.captured.append(
715+
CapturedData(
716+
path=request.path,
717+
event=event,
718+
envelope=envelope,
719+
compressed=compressed,
720+
)
721+
)
722+
723+
response = Response(status=self.code)
724+
response.headers.extend(self.headers)
725+
return response(environ, start_response)

tests/test_gevent.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import logging
2+
import pickle
3+
from datetime import datetime, timezone
4+
5+
import sentry_sdk
6+
from sentry_sdk._compat import PY37, PY38
7+
8+
import pytest
9+
from tests.conftest import CapturingServer
10+
11+
pytest.importorskip("gevent")
12+
13+
14+
@pytest.fixture(scope="module")
15+
def monkeypatched_gevent():
16+
try:
17+
import gevent
18+
19+
gevent.monkey.patch_all()
20+
except Exception as e:
21+
if "_RLock__owner" in str(e):
22+
pytest.skip("https://github.com/gevent/gevent/issues/1380")
23+
else:
24+
raise
25+
26+
27+
@pytest.fixture
28+
def capturing_server(request):
29+
server = CapturingServer()
30+
server.start()
31+
request.addfinalizer(server.stop)
32+
return server
33+
34+
35+
@pytest.fixture
36+
def make_client(request, capturing_server):
37+
def inner(**kwargs):
38+
return sentry_sdk.Client(
39+
"http://foobar@{}/132".format(capturing_server.url[len("http://") :]),
40+
**kwargs,
41+
)
42+
43+
return inner
44+
45+
46+
@pytest.mark.forked
47+
@pytest.mark.parametrize("debug", (True, False))
48+
@pytest.mark.parametrize("client_flush_method", ["close", "flush"])
49+
@pytest.mark.parametrize("use_pickle", (True, False))
50+
@pytest.mark.parametrize("compression_level", (0, 9, None))
51+
@pytest.mark.parametrize(
52+
"compression_algo",
53+
(("gzip", "br", "<invalid>", None) if PY37 else ("gzip", "<invalid>", None)),
54+
)
55+
@pytest.mark.parametrize("http2", [True, False] if PY38 else [False])
56+
def test_transport_works_gevent(
57+
capturing_server,
58+
request,
59+
capsys,
60+
caplog,
61+
debug,
62+
make_client,
63+
client_flush_method,
64+
use_pickle,
65+
compression_level,
66+
compression_algo,
67+
http2,
68+
):
69+
caplog.set_level(logging.DEBUG)
70+
71+
experiments = {}
72+
if compression_level is not None:
73+
experiments["transport_compression_level"] = compression_level
74+
75+
if compression_algo is not None:
76+
experiments["transport_compression_algo"] = compression_algo
77+
78+
if http2:
79+
experiments["transport_http2"] = True
80+
81+
client = make_client(
82+
debug=debug,
83+
_experiments=experiments,
84+
)
85+
86+
if use_pickle:
87+
client = pickle.loads(pickle.dumps(client))
88+
89+
sentry_sdk.get_global_scope().set_client(client)
90+
request.addfinalizer(lambda: sentry_sdk.get_global_scope().set_client(None))
91+
92+
sentry_sdk.add_breadcrumb(
93+
level="info", message="i like bread", timestamp=datetime.now(timezone.utc)
94+
)
95+
sentry_sdk.capture_message("löl")
96+
97+
getattr(client, client_flush_method)()
98+
99+
out, err = capsys.readouterr()
100+
assert not err and not out
101+
assert capturing_server.captured
102+
should_compress = (
103+
# default is to compress with brotli if available, gzip otherwise
104+
(compression_level is None)
105+
or (
106+
# setting compression level to 0 means don't compress
107+
compression_level
108+
> 0
109+
)
110+
) and (
111+
# if we couldn't resolve to a known algo, we don't compress
112+
compression_algo
113+
!= "<invalid>"
114+
)
115+
116+
assert capturing_server.captured[0].compressed == should_compress
117+
118+
assert any("Sending envelope" in record.msg for record in caplog.records) == debug

tests/test_transport.py

Lines changed: 13 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
11
import logging
22
import pickle
3-
import gzip
4-
import io
53
import os
64
import socket
75
import sys
8-
from collections import defaultdict, namedtuple
6+
from collections import defaultdict
97
from datetime import datetime, timedelta, timezone
108
from unittest import mock
119

12-
import brotli
1310
import pytest
14-
from pytest_localserver.http import WSGIServer
15-
from werkzeug.wrappers import Request, Response
11+
from tests.conftest import CapturingServer
1612

1713
try:
1814
import httpcore
1915
except (ImportError, ModuleNotFoundError):
2016
httpcore = None
2117

22-
try:
23-
import gevent
24-
except ImportError:
25-
gevent = None
26-
2718
import sentry_sdk
2819
from sentry_sdk import (
2920
Client,
@@ -42,65 +33,22 @@
4233
)
4334
from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
4435

45-
CapturedData = namedtuple("CapturedData", ["path", "event", "envelope", "compressed"])
46-
47-
48-
class CapturingServer(WSGIServer):
49-
def __init__(self, host="127.0.0.1", port=0, ssl_context=None):
50-
WSGIServer.__init__(self, host, port, self, ssl_context=ssl_context)
51-
self.code = 204
52-
self.headers = {}
53-
self.captured = []
54-
55-
def respond_with(self, code=200, headers=None):
56-
self.code = code
57-
if headers:
58-
self.headers = headers
59-
60-
def clear_captured(self):
61-
del self.captured[:]
62-
63-
def __call__(self, environ, start_response):
64-
"""
65-
This is the WSGI application.
66-
"""
67-
request = Request(environ)
68-
event = envelope = None
69-
content_encoding = request.headers.get("content-encoding")
70-
if content_encoding == "gzip":
71-
rdr = gzip.GzipFile(fileobj=io.BytesIO(request.data))
72-
compressed = True
73-
elif content_encoding == "br":
74-
rdr = io.BytesIO(brotli.decompress(request.data))
75-
compressed = True
76-
else:
77-
rdr = io.BytesIO(request.data)
78-
compressed = False
79-
80-
if request.mimetype == "application/json":
81-
event = parse_json(rdr.read())
82-
else:
83-
envelope = Envelope.deserialize_from(rdr)
84-
85-
self.captured.append(
86-
CapturedData(
87-
path=request.path,
88-
event=event,
89-
envelope=envelope,
90-
compressed=compressed,
91-
)
92-
)
9336

94-
response = Response(status=self.code)
95-
response.headers.extend(self.headers)
96-
return response(environ, start_response)
37+
server = None
9738

9839

99-
@pytest.fixture
100-
def capturing_server(request):
40+
@pytest.fixture(scope="module", autouse=True)
41+
def make_capturing_server(request):
42+
global server
10143
server = CapturingServer()
10244
server.start()
10345
request.addfinalizer(server.stop)
46+
47+
48+
@pytest.fixture
49+
def capturing_server():
50+
global server
51+
server.clear_captured()
10452
return server
10553

10654

@@ -129,18 +77,13 @@ def mock_transaction_envelope(span_count):
12977
return envelope
13078

13179

132-
@pytest.mark.forked
13380
@pytest.mark.parametrize("debug", (True, False))
13481
@pytest.mark.parametrize("client_flush_method", ["close", "flush"])
13582
@pytest.mark.parametrize("use_pickle", (True, False))
13683
@pytest.mark.parametrize("compression_level", (0, 9, None))
13784
@pytest.mark.parametrize(
13885
"compression_algo",
139-
(
140-
("gzip", "br", "<invalid>", None)
141-
if PY37 or gevent is None
142-
else ("gzip", "<invalid>", None)
143-
),
86+
(("gzip", "br", "<invalid>", None) if PY37 else ("gzip", "<invalid>", None)),
14487
)
14588
@pytest.mark.parametrize("http2", [True, False] if PY38 else [False])
14689
def test_transport_works(
@@ -155,7 +98,6 @@ def test_transport_works(
15598
compression_level,
15699
compression_algo,
157100
http2,
158-
maybe_monkeypatched_threading,
159101
):
160102
caplog.set_level(logging.DEBUG)
161103

0 commit comments

Comments
 (0)