Skip to content

Commit a4c16d7

Browse files
test(stdlib): Stop mocking HTTPSConnection.send in trace header tests (#6156)
Emulate basic HTTP/1.1 response in `HTTPSConnection` subclass instead of inspecting `mock.Mock` arguments.
1 parent ebe6f2a commit a4c16d7

1 file changed

Lines changed: 121 additions & 85 deletions

File tree

tests/integrations/stdlib/test_httplib.py

Lines changed: 121 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import socket
23
import datetime
34
from http.client import HTTPConnection, HTTPSConnection
45
from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -202,15 +203,31 @@ def test_httplib_misuse(sentry_init, capture_events, request):
202203
)
203204

204205

205-
def test_outgoing_trace_headers(sentry_init, monkeypatch):
206-
# HTTPSConnection.send is passed a string containing (among other things)
207-
# the headers on the request. Mock it so we can check the headers, and also
208-
# so it doesn't try to actually talk to the internet.
209-
mock_send = mock.Mock()
210-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
211-
206+
def test_outgoing_trace_headers(sentry_init, capture_events):
212207
sentry_init(traces_sample_rate=1.0)
213208

209+
already_patched_getresponse = HTTPSConnection.getresponse
210+
211+
request_headers = {}
212+
213+
class HTTPSConnectionRecordingRequestHeaders(HTTPSConnection):
214+
def send(self, *args, **kwargs) -> None:
215+
request_str = args[0]
216+
for line in request_str.decode("utf-8").split("\r\n")[1:]:
217+
if line:
218+
key, val = line.split(": ")
219+
request_headers[key] = val
220+
221+
server_sock, client_sock = socket.socketpair()
222+
server_sock.sendall(b"HTTP/1.1 200 OK\r\n\r\n")
223+
server_sock.close()
224+
self.sock = client_sock
225+
226+
def getresponse(self, *args, **kwargs):
227+
return already_patched_getresponse(self, *args, **kwargs)
228+
229+
events = capture_events()
230+
214231
headers = {
215232
"sentry-trace": "771a43a4192642f0b136d5159a501700-1234567890abcdef-1",
216233
"baggage": (
@@ -228,73 +245,83 @@ def test_outgoing_trace_headers(sentry_init, monkeypatch):
228245
op="greeting.sniff",
229246
trace_id="12312012123120121231201212312012",
230247
) as transaction:
231-
HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")
232-
233-
(request_str,) = mock_send.call_args[0]
234-
request_headers = {}
235-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
236-
if line:
237-
key, val = line.split(": ")
238-
request_headers[key] = val
239-
240-
request_span = transaction._span_recorder.spans[-1]
241-
expected_sentry_trace = "{trace_id}-{parent_span_id}-{sampled}".format(
242-
trace_id=transaction.trace_id,
243-
parent_span_id=request_span.span_id,
244-
sampled=1,
245-
)
246-
assert request_headers["sentry-trace"] == expected_sentry_trace
247-
248-
expected_outgoing_baggage = (
249-
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
250-
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
251-
"sentry-sample_rate=1.0,"
252-
"sentry-user_id=Am%C3%A9lie,"
253-
"sentry-sample_rand=0.132521102938283"
254-
)
248+
connection = HTTPSConnectionRecordingRequestHeaders("localhost", port=PORT)
249+
connection.request("GET", "/top-chasers")
250+
connection.getresponse()
255251

256-
assert request_headers["baggage"] == expected_outgoing_baggage
252+
(event,) = events
253+
request_span = event["spans"][-1]
254+
expected_sentry_trace = "{trace_id}-{parent_span_id}-{sampled}".format(
255+
trace_id=event["contexts"]["trace"]["trace_id"],
256+
parent_span_id=request_span["span_id"],
257+
sampled=1,
258+
)
259+
assert request_headers["sentry-trace"] == expected_sentry_trace
260+
261+
expected_outgoing_baggage = (
262+
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
263+
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
264+
"sentry-sample_rate=1.0,"
265+
"sentry-user_id=Am%C3%A9lie,"
266+
"sentry-sample_rand=0.132521102938283"
267+
)
257268

269+
assert request_headers["baggage"] == expected_outgoing_baggage
258270

259-
def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch):
260-
# HTTPSConnection.send is passed a string containing (among other things)
261-
# the headers on the request. Mock it so we can check the headers, and also
262-
# so it doesn't try to actually talk to the internet.
263-
mock_send = mock.Mock()
264-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
265271

272+
def test_outgoing_trace_headers_head_sdk(sentry_init, capture_events):
266273
sentry_init(traces_sample_rate=0.5, release="foo")
274+
275+
already_patched_getresponse = HTTPSConnection.getresponse
276+
277+
request_headers = {}
278+
279+
class HTTPSConnectionRecordingRequestHeaders(HTTPSConnection):
280+
def send(self, *args, **kwargs) -> None:
281+
request_str = args[0]
282+
for line in request_str.decode("utf-8").split("\r\n")[1:]:
283+
if line:
284+
key, val = line.split(": ")
285+
request_headers[key] = val
286+
287+
server_sock, client_sock = socket.socketpair()
288+
server_sock.sendall(b"HTTP/1.1 200 OK\r\n\r\n")
289+
server_sock.close()
290+
self.sock = client_sock
291+
292+
def getresponse(self, *args, **kwargs):
293+
return already_patched_getresponse(self, *args, **kwargs)
294+
295+
events = capture_events()
296+
267297
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=250000):
268298
transaction = continue_trace({})
269299

270300
with start_transaction(transaction=transaction, name="Head SDK tx") as transaction:
271-
HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")
272-
273-
(request_str,) = mock_send.call_args[0]
274-
request_headers = {}
275-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
276-
if line:
277-
key, val = line.split(": ")
278-
request_headers[key] = val
279-
280-
request_span = transaction._span_recorder.spans[-1]
281-
expected_sentry_trace = "{trace_id}-{parent_span_id}-{sampled}".format(
282-
trace_id=transaction.trace_id,
283-
parent_span_id=request_span.span_id,
284-
sampled=1,
285-
)
286-
assert request_headers["sentry-trace"] == expected_sentry_trace
301+
connection = HTTPSConnectionRecordingRequestHeaders("localhost", port=PORT)
302+
connection.request("GET", "/top-chasers")
303+
connection.getresponse()
304+
305+
(event,) = events
306+
request_span = event["spans"][-1]
307+
expected_sentry_trace = "{trace_id}-{parent_span_id}-{sampled}".format(
308+
trace_id=event["contexts"]["trace"]["trace_id"],
309+
parent_span_id=request_span["span_id"],
310+
sampled=1,
311+
)
312+
313+
assert request_headers["sentry-trace"] == expected_sentry_trace
287314

288-
expected_outgoing_baggage = (
289-
"sentry-trace_id=%s,"
290-
"sentry-sample_rand=0.250000,"
291-
"sentry-environment=production,"
292-
"sentry-release=foo,"
293-
"sentry-sample_rate=0.5,"
294-
"sentry-sampled=%s"
295-
) % (transaction.trace_id, "true" if transaction.sampled else "false")
315+
expected_outgoing_baggage = (
316+
"sentry-trace_id=%s,"
317+
"sentry-sample_rand=0.250000,"
318+
"sentry-environment=production,"
319+
"sentry-release=foo,"
320+
"sentry-sample_rate=0.5,"
321+
"sentry-sampled=%s"
322+
) % (transaction.trace_id, "true" if transaction.sampled else "false")
296323

297-
assert request_headers["baggage"] == expected_outgoing_baggage
324+
assert request_headers["baggage"] == expected_outgoing_baggage
298325

299326

300327
@pytest.mark.parametrize(
@@ -357,19 +384,33 @@ def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch):
357384
],
358385
)
359386
def test_option_trace_propagation_targets(
360-
sentry_init, monkeypatch, trace_propagation_targets, host, path, trace_propagated
387+
sentry_init, trace_propagation_targets, host, path, trace_propagated
361388
):
362-
# HTTPSConnection.send is passed a string containing (among other things)
363-
# the headers on the request. Mock it so we can check the headers, and also
364-
# so it doesn't try to actually talk to the internet.
365-
mock_send = mock.Mock()
366-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
367-
368389
sentry_init(
369390
trace_propagation_targets=trace_propagation_targets,
370391
traces_sample_rate=1.0,
371392
)
372393

394+
already_patched_getresponse = HTTPSConnection.getresponse
395+
396+
request_headers = {}
397+
398+
class HTTPSConnectionRecordingRequestHeaders(HTTPSConnection):
399+
def send(self, *args, **kwargs) -> None:
400+
request_str = args[0]
401+
for line in request_str.decode("utf-8").split("\r\n")[1:]:
402+
if line:
403+
key, val = line.split(": ")
404+
request_headers[key] = val
405+
406+
server_sock, client_sock = socket.socketpair()
407+
server_sock.sendall(b"HTTP/1.1 200 OK\r\n\r\n")
408+
server_sock.close()
409+
self.sock = client_sock
410+
411+
def getresponse(self, *args, **kwargs):
412+
return already_patched_getresponse(self, *args, **kwargs)
413+
373414
headers = {
374415
"baggage": (
375416
"sentry-trace_id=771a43a4192642f0b136d5159a501700, "
@@ -385,21 +426,16 @@ def test_option_trace_propagation_targets(
385426
op="greeting.sniff",
386427
trace_id="12312012123120121231201212312012",
387428
) as transaction:
388-
HTTPSConnection(host).request("GET", path)
389-
390-
(request_str,) = mock_send.call_args[0]
391-
request_headers = {}
392-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
393-
if line:
394-
key, val = line.split(": ")
395-
request_headers[key] = val
396-
397-
if trace_propagated:
398-
assert "sentry-trace" in request_headers
399-
assert "baggage" in request_headers
400-
else:
401-
assert "sentry-trace" not in request_headers
402-
assert "baggage" not in request_headers
429+
connection = HTTPSConnectionRecordingRequestHeaders(host)
430+
connection.request("GET", path)
431+
connection.getresponse()
432+
433+
if trace_propagated:
434+
assert "sentry-trace" in request_headers
435+
assert "baggage" in request_headers
436+
else:
437+
assert "sentry-trace" not in request_headers
438+
assert "baggage" not in request_headers
403439

404440

405441
def test_request_source_disabled(sentry_init, capture_events):

0 commit comments

Comments
 (0)