Skip to content

Commit 9876504

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

File tree

3 files changed

+190
-72
lines changed

3 files changed

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