|
3 | 3 | import subprocess
|
4 | 4 | import sys
|
5 | 5 | import time
|
| 6 | +from collections import Counter, defaultdict |
6 | 7 | from collections.abc import Mapping
|
7 | 8 | from textwrap import dedent
|
8 | 9 | from unittest import mock
|
@@ -1214,3 +1215,159 @@ def test_uwsgi_warnings(sentry_init, recwarn, opt, missing_flags):
|
1214 | 1215 | assert flag in str(record.message)
|
1215 | 1216 | else:
|
1216 | 1217 | assert not recwarn
|
| 1218 | + |
| 1219 | + |
| 1220 | +class TestSpanClientReports: |
| 1221 | + """ |
| 1222 | + Tests for client reports related to spans. |
| 1223 | + """ |
| 1224 | + |
| 1225 | + @staticmethod |
| 1226 | + def span_dropper(spans_to_drop): |
| 1227 | + """ |
| 1228 | + Returns a function that can be used to drop spans from an event. |
| 1229 | + """ |
| 1230 | + |
| 1231 | + def drop_spans(event, _): |
| 1232 | + event["spans"] = event["spans"][spans_to_drop:] |
| 1233 | + return event |
| 1234 | + |
| 1235 | + return drop_spans |
| 1236 | + |
| 1237 | + @staticmethod |
| 1238 | + def mock_transaction_event(span_count): |
| 1239 | + """ |
| 1240 | + Returns a mock transaction event with the given number of spans. |
| 1241 | + """ |
| 1242 | + |
| 1243 | + return defaultdict( |
| 1244 | + mock.MagicMock, |
| 1245 | + type="transaction", |
| 1246 | + spans=[mock.MagicMock() for _ in range(span_count)], |
| 1247 | + ) |
| 1248 | + |
| 1249 | + def __init__(self, span_count): |
| 1250 | + """Configures a test case with the number of spans dropped and whether the transaction was dropped.""" |
| 1251 | + self.span_count = span_count |
| 1252 | + self.expected_record_lost_event_calls = Counter() |
| 1253 | + self.before_send = lambda event, _: event |
| 1254 | + self.event_processor = lambda event, _: event |
| 1255 | + |
| 1256 | + def _update_resulting_calls(self, reason, drops_transactions=0, drops_spans=0): |
| 1257 | + """ |
| 1258 | + Updates the expected calls with the given resulting calls. |
| 1259 | + """ |
| 1260 | + if drops_transactions > 0: |
| 1261 | + self.expected_record_lost_event_calls[ |
| 1262 | + (reason, "transaction", None, drops_transactions) |
| 1263 | + ] += 1 |
| 1264 | + |
| 1265 | + if drops_spans > 0: |
| 1266 | + self.expected_record_lost_event_calls[ |
| 1267 | + (reason, "span", None, drops_spans) |
| 1268 | + ] += 1 |
| 1269 | + |
| 1270 | + def with_before_send( |
| 1271 | + self, |
| 1272 | + before_send, |
| 1273 | + *, |
| 1274 | + drops_transactions=0, |
| 1275 | + drops_spans=0, |
| 1276 | + ): |
| 1277 | + self.before_send = before_send |
| 1278 | + self._update_resulting_calls( |
| 1279 | + "before_send", |
| 1280 | + drops_transactions, |
| 1281 | + drops_spans, |
| 1282 | + ) |
| 1283 | + |
| 1284 | + return self |
| 1285 | + |
| 1286 | + def with_event_processor( |
| 1287 | + self, |
| 1288 | + event_processor, |
| 1289 | + *, |
| 1290 | + drops_transactions=0, |
| 1291 | + drops_spans=0, |
| 1292 | + ): |
| 1293 | + self.event_processor = event_processor |
| 1294 | + self._update_resulting_calls( |
| 1295 | + "event_processor", |
| 1296 | + drops_transactions, |
| 1297 | + drops_spans, |
| 1298 | + ) |
| 1299 | + |
| 1300 | + return self |
| 1301 | + |
| 1302 | + def run(self, sentry_init, capture_record_lost_event_calls): |
| 1303 | + """Runs the test case with the configured parameters.""" |
| 1304 | + sentry_init(before_send_transaction=self.before_send) |
| 1305 | + record_lost_event_calls = capture_record_lost_event_calls() |
| 1306 | + |
| 1307 | + with sentry_sdk.isolation_scope() as scope: |
| 1308 | + scope.add_event_processor(self.event_processor) |
| 1309 | + event = self.mock_transaction_event(self.span_count) |
| 1310 | + sentry_sdk.get_client().capture_event(event, scope=scope) |
| 1311 | + |
| 1312 | + # We use counters to ensure that the calls are made the expected number of times, disregarding order. |
| 1313 | + assert Counter(record_lost_event_calls) == self.expected_record_lost_event_calls |
| 1314 | + |
| 1315 | + |
| 1316 | +@pytest.mark.parametrize( |
| 1317 | + "test_config", |
| 1318 | + ( |
| 1319 | + TestSpanClientReports(span_count=10), # No spans dropped |
| 1320 | + TestSpanClientReports(span_count=0).with_before_send( |
| 1321 | + lambda e, _: None, |
| 1322 | + drops_transactions=1, |
| 1323 | + drops_spans=1, |
| 1324 | + ), |
| 1325 | + TestSpanClientReports(span_count=10).with_before_send( |
| 1326 | + lambda e, _: None, |
| 1327 | + drops_transactions=1, |
| 1328 | + drops_spans=11, |
| 1329 | + ), |
| 1330 | + TestSpanClientReports(span_count=10).with_before_send( |
| 1331 | + TestSpanClientReports.span_dropper(3), |
| 1332 | + drops_spans=3, |
| 1333 | + ), |
| 1334 | + TestSpanClientReports(span_count=10).with_before_send( |
| 1335 | + TestSpanClientReports.span_dropper(10), |
| 1336 | + drops_spans=10, |
| 1337 | + ), |
| 1338 | + TestSpanClientReports(span_count=10).with_event_processor( |
| 1339 | + lambda e, _: None, |
| 1340 | + drops_transactions=1, |
| 1341 | + drops_spans=11, |
| 1342 | + ), |
| 1343 | + TestSpanClientReports(span_count=10).with_event_processor( |
| 1344 | + TestSpanClientReports.span_dropper(3), |
| 1345 | + drops_spans=3, |
| 1346 | + ), |
| 1347 | + TestSpanClientReports(span_count=10).with_event_processor( |
| 1348 | + TestSpanClientReports.span_dropper(10), |
| 1349 | + drops_spans=10, |
| 1350 | + ), |
| 1351 | + TestSpanClientReports(span_count=10) |
| 1352 | + .with_event_processor( |
| 1353 | + TestSpanClientReports.span_dropper(3), |
| 1354 | + drops_spans=3, |
| 1355 | + ) |
| 1356 | + .with_before_send( |
| 1357 | + TestSpanClientReports.span_dropper(5), |
| 1358 | + drops_spans=5, |
| 1359 | + ), |
| 1360 | + TestSpanClientReports(10) |
| 1361 | + .with_event_processor( |
| 1362 | + TestSpanClientReports.span_dropper(3), |
| 1363 | + drops_spans=3, |
| 1364 | + ) |
| 1365 | + .with_before_send( |
| 1366 | + lambda e, _: None, |
| 1367 | + drops_transactions=1, |
| 1368 | + drops_spans=8, # 3 of the 11 (incl. transaction) spans already dropped |
| 1369 | + ), |
| 1370 | + ), |
| 1371 | +) |
| 1372 | +def test_dropped_transaction(sentry_init, capture_record_lost_event_calls, test_config): |
| 1373 | + test_config.run(sentry_init, capture_record_lost_event_calls) |
0 commit comments