Skip to content

Commit 52bc345

Browse files
authored
fix(tests): Make test_failures a Queue (#4112)
Prevent manual `sleep` calls when waiting for `mini_sentry.test_failures`. Because `sleep` blocks the tests unconditionally, we tend to choose short timeouts which then produce flakiness if the event we're waiting on does not occur fast enough. By making `test_failures` a `queue.Queue`, we can set longer timeouts while at the same time shortening the actual sleep time in most cases.
1 parent 4487c7b commit 52bc345

File tree

10 files changed

+65
-56
lines changed

10 files changed

+65
-56
lines changed

tests/integration/fixtures/mini_sentry.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import uuid
88
from copy import deepcopy
9-
from queue import Queue
9+
from queue import Empty, Queue
1010

1111
import pytest
1212

@@ -41,8 +41,7 @@ def __init__(self, server_address, app):
4141
self.captured_events = Queue()
4242
self.captured_outcomes = Queue()
4343
self.captured_metrics = Queue()
44-
self.test_failures = []
45-
self.reraise_test_failures = True
44+
self.test_failures = Queue()
4645
self.hits = {}
4746
self.known_relays = {}
4847
self.fail_on_relay_error = True
@@ -64,9 +63,21 @@ def hit(self, path):
6463
self.hits.setdefault(path, 0)
6564
self.hits[path] += 1
6665

66+
def current_test_failures(self):
67+
"""Return current list of test failures without waiting for additional failures."""
68+
try:
69+
while failure := self.test_failures.get_nowait():
70+
yield failure
71+
except Empty:
72+
return
73+
74+
def clear_test_failures(self):
75+
"""Reset test failures to an empty queue."""
76+
self.test_failures = Queue()
77+
6778
def format_failures(self):
6879
s = ""
69-
for route, error in self.test_failures:
80+
for route, error in self.current_test_failures():
7081
s += f"> {route}: {error}\n"
7182
return s
7283

@@ -296,7 +307,7 @@ def store_internal_error_event():
296307

297308
if event is not None and sentry.fail_on_relay_error:
298309
error = AssertionError("Relay sent us event: " + get_error_message(event))
299-
sentry.test_failures.append(("/api/666/envelope/", error))
310+
sentry.test_failures.put(("/api/666/envelope/", error))
300311

301312
return jsonify({"event_id": uuid.uuid4().hex})
302313

@@ -452,10 +463,10 @@ def fail(e):
452463
raise e
453464

454465
def reraise_test_failures():
455-
if sentry.test_failures and sentry.reraise_test_failures:
466+
if not sentry.test_failures.empty():
456467
pytest.fail(
457468
"{n} exceptions happened in mini_sentry:\n\n{failures}".format(
458-
n=len(sentry.test_failures), failures=sentry.format_failures()
469+
n=sentry.test_failures.qsize(), failures=sentry.format_failures()
459470
)
460471
)
461472

tests/integration/fixtures/relay.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
from queue import Queue
34
import sys
45
import uuid
56
import signal
@@ -211,11 +212,11 @@ def inner(
211212
relay.wait_relay_health_check()
212213

213214
# Filter out health check failures, which can happen during startup
214-
mini_sentry.test_failures = [
215-
f
216-
for f in mini_sentry.test_failures
217-
if "Health check probe" not in str(f)
218-
]
215+
filtered_test_failures = Queue()
216+
for f in mini_sentry.current_test_failures():
217+
if "Health check probe" not in str(f):
218+
filtered_test_failures.put(f)
219+
mini_sentry.test_failures = filtered_test_failures
219220

220221
return relay
221222

tests/integration/test_basic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def get_project_config():
110110
relay.shutdown(sig=signal.SIGINT)
111111
pytest.raises(queue.Empty, lambda: mini_sentry.captured_events.get(timeout=1))
112112

113-
failures = mini_sentry.test_failures
113+
failures = mini_sentry.current_test_failures()
114114
assert failures
115115

116116
# we are expecting at least a dropped unfinished future error
@@ -121,7 +121,7 @@ def get_project_config():
121121
dropped_unfinished_error_found = True
122122
assert dropped_unfinished_error_found
123123
finally:
124-
mini_sentry.test_failures.clear()
124+
mini_sentry.clear_test_failures()
125125

126126

127127
@pytest.mark.parametrize("trailing_slash", [True, False])

tests/integration/test_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ def test_invalid_kafka_config_should_fail(mini_sentry, relay_with_processing):
1414
relay = relay_with_processing(options=options, wait_health_check=False)
1515
assert relay.wait_for_exit() != 0
1616

17-
error = str(mini_sentry.test_failures.pop(0))
17+
error = str(mini_sentry.test_failures.get_nowait())
1818
assert "__unknown" in error
19-
error = str(mini_sentry.test_failures.pop(0))
19+
error = str(mini_sentry.test_failures.get_nowait())
2020
assert "profiles" in error.lower()
2121

2222

@@ -26,5 +26,5 @@ def test_invalid_topics_raise_error(mini_sentry, relay_with_processing):
2626
relay = relay_with_processing(options=options, wait_health_check=False)
2727
assert relay.wait_for_exit() != 0
2828

29-
error = str(mini_sentry.test_failures.pop(0))
29+
error = str(mini_sentry.test_failures.get_nowait())
3030
assert "failed to validate the topic with name" in error

tests/integration/test_envelope.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,12 +465,12 @@ def get_project_config():
465465

466466
include_global = True
467467
# Clear errors because we log error when we request global config yet we dont receive it.
468-
assert len(mini_sentry.test_failures) > 0
469-
assert {str(e) for _, e in mini_sentry.test_failures} == {
468+
assert not mini_sentry.test_failures.empty()
469+
assert {str(e) for _, e in mini_sentry.current_test_failures()} == {
470470
"Relay sent us event: global config missing in upstream response"
471471
}
472472
finally:
473-
mini_sentry.test_failures.clear()
473+
mini_sentry.clear_test_failures()
474474

475475
envelopes = []
476476
# Check that we received exactly {envelope_qty} envelopes.

tests/integration/test_healthchecks.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def test_readiness(mini_sentry, relay):
5353
mini_sentry.app.view_functions["check_challenge"] = original_check_challenge
5454
relay.wait_relay_health_check()
5555
finally:
56-
mini_sentry.test_failures.clear()
56+
mini_sentry.clear_test_failures()
5757

5858
response = relay.get("/api/relay/healthcheck/ready/")
5959
assert response.status_code == 200
@@ -69,7 +69,7 @@ def test_readiness_flag(mini_sentry, relay):
6969
response = wait_get(relay, "/api/relay/healthcheck/ready/")
7070
assert response.status_code == 200
7171
finally:
72-
mini_sentry.test_failures.clear()
72+
mini_sentry.clear_test_failures()
7373

7474

7575
def test_readiness_proxy(mini_sentry, relay):
@@ -88,12 +88,11 @@ def test_readiness_not_enough_memory_bytes(mini_sentry, relay):
8888
)
8989

9090
response = wait_get(relay, "/api/relay/healthcheck/ready/")
91-
time.sleep(1.0) # Wait for error
92-
error = str(mini_sentry.test_failures.pop(0))
91+
error = str(mini_sentry.test_failures.get(timeout=2))
9392
assert "Not enough memory" in error and ">= 42" in error
94-
error = str(mini_sentry.test_failures.pop(0))
93+
error = str(mini_sentry.test_failures.get(timeout=1))
9594
assert "Health check probe 'system memory'" in error
96-
error = str(mini_sentry.test_failures.pop(0))
95+
error = str(mini_sentry.test_failures.get(timeout=1))
9796
assert "Health check probe 'spool health'" in error
9897
assert response.status_code == 503
9998

@@ -105,12 +104,12 @@ def test_readiness_not_enough_memory_percent(mini_sentry, relay):
105104
wait_health_check=False,
106105
)
107106
response = wait_get(relay, "/api/relay/healthcheck/ready/")
108-
time.sleep(1.0) # Wait for error
109-
error = str(mini_sentry.test_failures.pop(0))
107+
108+
error = str(mini_sentry.test_failures.get(timeout=2))
110109
assert "Not enough memory" in error and ">= 1.00%" in error
111-
error = str(mini_sentry.test_failures.pop(0))
110+
error = str(mini_sentry.test_failures.get(timeout=1))
112111
assert "Health check probe 'system memory'" in error
113-
error = str(mini_sentry.test_failures.pop(0))
112+
error = str(mini_sentry.test_failures.get(timeout=1))
114113
assert "Health check probe 'spool health'" in error
115114
assert response.status_code == 503
116115

@@ -123,8 +122,8 @@ def test_readiness_depends_on_aggregator_being_full(mini_sentry, relay):
123122
)
124123

125124
response = wait_get(relay, "/api/relay/healthcheck/ready/")
126-
time.sleep(0.3) # Wait for error
127-
error = str(mini_sentry.test_failures.pop())
125+
126+
error = str(mini_sentry.test_failures.get(timeout=1))
128127
assert "Health check probe 'aggregator'" in error
129128
assert response.status_code == 503
130129

@@ -140,20 +139,19 @@ def test_readiness_depends_on_aggregator_being_full_after_metrics(mini_sentry, r
140139

141140
for _ in range(100):
142141
response = wait_get(relay, "/api/relay/healthcheck/ready/")
143-
print(response, response.status_code)
144142
if response.status_code == 503:
145-
error = str(mini_sentry.test_failures.pop())
146-
assert "Health check probe 'aggregator'" in error
147-
error = str(mini_sentry.test_failures.pop())
143+
error = str(mini_sentry.test_failures.get(timeout=1))
148144
assert "aggregator limit exceeded" in error
145+
error = str(mini_sentry.test_failures.get(timeout=1))
146+
assert "Health check probe 'aggregator'" in error
149147
return
150148
time.sleep(0.1)
151149

152150
assert False, "health check never failed"
153151

154152

155153
def test_readiness_disk_spool(mini_sentry, relay):
156-
mini_sentry.reraise_test_failures = False
154+
mini_sentry.fail_on_relay_error = False
157155
temp = tempfile.mkdtemp()
158156
dbfile = os.path.join(temp, "buffer.db")
159157

tests/integration/test_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ def test_transaction_metrics_not_extracted_on_unsupported_version(
11821182
tx_consumer.assert_empty()
11831183

11841184
if unsupported_version < TRANSACTION_EXTRACT_MIN_SUPPORTED_VERSION:
1185-
error = str(mini_sentry.test_failures.pop(0))
1185+
error = str(mini_sentry.test_failures.get_nowait())
11861186
assert "Processing Relay outdated" in error
11871187

11881188
metrics_consumer.assert_empty()

tests/integration/test_projectconfigs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,11 @@ def test_unparsable_project_config(mini_sentry, relay):
260260

261261
def assert_clear_test_failures():
262262
try:
263-
assert {str(e) for _, e in mini_sentry.test_failures} == {
263+
assert {str(e) for _, e in mini_sentry.current_test_failures()} == {
264264
f"Relay sent us event: error fetching project state {public_key}: invalid type: integer `99`, expected a string",
265265
}
266266
finally:
267-
mini_sentry.test_failures.clear()
267+
mini_sentry.clear_test_failures()
268268

269269
# Event is not propagated, relay logs an error:
270270
relay.send_event(project_key)
@@ -343,11 +343,11 @@ def test_cached_project_config(mini_sentry, relay):
343343
try:
344344
relay.send_event(project_key)
345345
time.sleep(0.5)
346-
assert {str(e) for _, e in mini_sentry.test_failures} == {
346+
assert {str(e) for _, e in mini_sentry.current_test_failures()} == {
347347
f"Relay sent us event: error fetching project state {public_key}: invalid type: integer `99`, expected a string",
348348
}
349349
finally:
350-
mini_sentry.test_failures.clear()
350+
mini_sentry.clear_test_failures()
351351

352352

353353
def test_get_global_config(mini_sentry, relay):

tests/integration/test_query.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_local_project_config(mini_sentry, relay):
3838
dsn_key = config["publicKeys"][0]["publicKey"]
3939

4040
relay.wait_relay_health_check()
41-
mini_sentry.test_failures.clear()
41+
mini_sentry.clear_test_failures()
4242

4343
relay.send_event(project_id, dsn_key=dsn_key)
4444
event = mini_sentry.captured_events.get(timeout=1).get_event()
@@ -148,11 +148,10 @@ def get_project_config():
148148
assert event["logentry"] == {"formatted": "Hello, World!"}
149149
assert retry_count == 3
150150

151-
if mini_sentry.test_failures:
152-
for _, error in mini_sentry.test_failures:
153-
assert isinstance(error, (socket.error, AssertionError))
151+
for _, error in mini_sentry.current_test_failures():
152+
assert isinstance(error, (socket.error, AssertionError))
154153
finally:
155-
mini_sentry.test_failures.clear()
154+
mini_sentry.clear_test_failures()
156155

157156

158157
def test_query_retry_maxed_out(mini_sentry, relay_with_processing, events_consumer):
@@ -190,18 +189,18 @@ def get_project_config():
190189
)
191190

192191
# No error messages yet
193-
assert not mini_sentry.test_failures
192+
assert mini_sentry.test_failures.empty()
194193

195194
try:
196195
relay.send_event(42)
197196
time.sleep(query_timeout)
198197

199198
assert request_count == 1 + RETRIES
200-
assert {str(e) for _, e in mini_sentry.test_failures} == {
199+
assert {str(e) for _, e in mini_sentry.current_test_failures()} == {
201200
"Relay sent us event: error fetching project states: upstream request returned error 500 Internal Server Error: no error details",
202201
}
203202
finally:
204-
mini_sentry.test_failures.clear()
203+
mini_sentry.clear_test_failures()
205204

206205

207206
@pytest.mark.parametrize("disabled", (True, False))

tests/integration/test_store.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def configure_static_project(dir):
193193
assert event["logentry"] == {"formatted": "Hello, World!"}
194194

195195
sleep(1) # Regression test: Relay tried to issue a request for 0 states
196-
if mini_sentry.test_failures:
196+
if not mini_sentry.test_failures.empty():
197197
raise AssertionError(
198198
f"Exceptions happened in mini_sentry: {mini_sentry.format_failures()}"
199199
)
@@ -230,15 +230,15 @@ def test_store_with_low_memory(mini_sentry, relay):
230230
pytest.raises(queue.Empty, lambda: mini_sentry.captured_events.get(timeout=1))
231231

232232
found_queue_error = False
233-
for _, error in mini_sentry.test_failures:
233+
for _, error in mini_sentry.current_test_failures():
234234
assert isinstance(error, AssertionError)
235235
if "failed to queue envelope" in str(error):
236236
found_queue_error = True
237237
break
238238

239239
assert found_queue_error
240240
finally:
241-
mini_sentry.test_failures.clear()
241+
mini_sentry.clear_test_failures()
242242

243243

244244
def test_store_max_concurrent_requests(mini_sentry, relay):
@@ -954,7 +954,7 @@ def server_error(*args, **kwargs):
954954
assert event["logentry"] == {"formatted": "Hello, World!"}
955955
finally:
956956
# Relay reports authentication errors, which is fine.
957-
mini_sentry.test_failures.clear()
957+
mini_sentry.clear_test_failures()
958958

959959

960960
def test_events_are_retried(relay, mini_sentry):
@@ -1181,7 +1181,7 @@ def counted_check_challenge(*args, **kwargs):
11811181
evt.clear()
11821182
assert evt.wait(2)
11831183
# clear authentication errors accumulated until now
1184-
mini_sentry.test_failures.clear()
1184+
mini_sentry.clear_test_failures()
11851185
# check that we have had some auth that succeeded
11861186
auth_count_3 = counter[0]
11871187
assert auth_count_2 < auth_count_3
@@ -1250,7 +1250,7 @@ def counted_check_challenge(*args, **kwargs):
12501250
# to be sure verify that we have only been called once (after failing)
12511251
assert counter[1] == 1
12521252
# clear authentication errors accumulated until now
1253-
mini_sentry.test_failures.clear()
1253+
mini_sentry.clear_test_failures()
12541254

12551255

12561256
def test_buffer_events_during_outage(relay, mini_sentry):

0 commit comments

Comments
 (0)