-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathmetrics_tests.py
More file actions
322 lines (277 loc) · 14.5 KB
/
Copy pathmetrics_tests.py
File metadata and controls
322 lines (277 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import unittest
from unittest.mock import patch
from tasks.static_quality_gates.metrics import (
GateMetricsData,
_extract_gate_name_from_scope,
_get_latest_value_from_pointlist,
fetch_main_headroom,
fetch_pr_metrics,
)
from tasks.static_quality_gates.thresholds import BUFFER_SIZE
class MockPoint:
"""Mock Point object matching datadog_api_client.v1.model.point.Point structure."""
def __init__(self, timestamp, metric_value):
self.value = [timestamp, metric_value]
def make_pointlist(points: list) -> list:
"""Convert [[timestamp, value], ...] to [MockPoint, ...] for tests."""
return [MockPoint(p[0], p[1]) for p in points]
class TestExceptionThresholdBumpHelpers(unittest.TestCase):
"""Test helper functions for the exception_threshold_bump implementation."""
def test_extract_gate_name_from_scope_valid(self):
"""Should extract gate name from scope string."""
scope = "gate_name:static_quality_gate_agent_deb_amd64,pr_number:12345"
result = _extract_gate_name_from_scope(scope)
self.assertEqual(result, "static_quality_gate_agent_deb_amd64")
def test_extract_gate_name_from_scope_single_tag(self):
"""Should extract gate name when it's the only tag."""
scope = "gate_name:static_quality_gate_docker_agent_arm64"
result = _extract_gate_name_from_scope(scope)
self.assertEqual(result, "static_quality_gate_docker_agent_arm64")
def test_extract_gate_name_from_scope_missing(self):
"""Should return None when gate_name is not in scope."""
scope = "pr_number:12345,arch:amd64"
result = _extract_gate_name_from_scope(scope)
self.assertIsNone(result)
def test_extract_gate_name_from_scope_empty(self):
"""Should return None for empty scope."""
result = _extract_gate_name_from_scope("")
self.assertIsNone(result)
def test_get_latest_value_from_pointlist_valid(self):
"""Should get the latest non-null value from pointlist."""
pointlist = make_pointlist([[1704067200, 100.0], [1704153600, 150.0], [1704240000, 200.0]])
result = _get_latest_value_from_pointlist(pointlist)
self.assertEqual(result, 200.0)
def test_get_latest_value_from_pointlist_with_nulls(self):
"""Should skip null values and get the latest non-null value."""
pointlist = make_pointlist([[1704067200, 100.0], [1704153600, 150.0], [1704240000, None]])
result = _get_latest_value_from_pointlist(pointlist)
self.assertEqual(result, 150.0)
def test_get_latest_value_from_pointlist_all_nulls(self):
"""Should return None if all values are null."""
pointlist = make_pointlist([[1704067200, None], [1704153600, None]])
result = _get_latest_value_from_pointlist(pointlist)
self.assertIsNone(result)
def test_get_latest_value_from_pointlist_empty(self):
"""Should return None for empty pointlist."""
result = _get_latest_value_from_pointlist([])
self.assertIsNone(result)
class TestFetchPrMetrics(unittest.TestCase):
"""Test the fetch_pr_metrics function."""
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_fetches_and_parses_metrics(self, mock_query):
"""Should fetch metrics and parse them correctly with single API call."""
# Single API call returns all 4 metrics
mock_query.return_value = [
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 100 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 50 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 150 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 75 * 1024 * 1024]]),
},
]
result = fetch_pr_metrics(12345)
# Should make exactly 1 API call
mock_query.assert_called_once()
self.assertEqual(len(result), 1)
self.assertIn("static_quality_gate_agent_deb_amd64", result)
gate = result["static_quality_gate_agent_deb_amd64"]
self.assertEqual(gate.current_on_disk_size, 100 * 1024 * 1024)
self.assertEqual(gate.current_on_wire_size, 50 * 1024 * 1024)
self.assertEqual(gate.max_on_disk_size, 150 * 1024 * 1024)
self.assertEqual(gate.max_on_wire_size, 75 * 1024 * 1024)
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_returns_empty_when_no_metrics(self, mock_query):
"""Should return empty dict when no metrics found."""
mock_query.return_value = []
result = fetch_pr_metrics(12345)
self.assertEqual(len(result), 0)
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_handles_multiple_gates(self, mock_query):
"""Should handle metrics for multiple gates in single API call."""
# Single API call returns metrics for multiple gates
mock_query.return_value = [
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 100 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_docker_agent_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 200 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 50 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_docker_agent_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 80 * 1024 * 1024]]),
},
]
result = fetch_pr_metrics(12345)
# Should make exactly 1 API call
mock_query.assert_called_once()
self.assertEqual(len(result), 2)
self.assertIn("static_quality_gate_agent_deb_amd64", result)
self.assertIn("static_quality_gate_docker_agent_amd64", result)
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_fetches_relative_size_metrics(self, mock_query):
"""Should fetch and parse relative_on_disk_size and relative_on_wire_size."""
# API call includes all 6 metrics including relative sizes
mock_query.return_value = [
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 100 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 50 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 150 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 75 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.relative_on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 5 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.relative_on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 2 * 1024 * 1024]]),
},
]
result = fetch_pr_metrics(12345)
self.assertEqual(len(result), 1)
gate = result["static_quality_gate_agent_deb_amd64"]
self.assertEqual(gate.current_on_disk_size, 100 * 1024 * 1024)
self.assertEqual(gate.current_on_wire_size, 50 * 1024 * 1024)
self.assertEqual(gate.max_on_disk_size, 150 * 1024 * 1024)
self.assertEqual(gate.max_on_wire_size, 75 * 1024 * 1024)
self.assertEqual(gate.relative_on_disk_size, 5 * 1024 * 1024)
self.assertEqual(gate.relative_on_wire_size, 2 * 1024 * 1024)
class TestFetchMainHeadroom(unittest.TestCase):
"""Test the fetch_main_headroom function."""
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_calculates_headroom_correctly(self, mock_query):
"""Should calculate headroom as max - current."""
# Single API call returns all 4 metrics for the gate
mock_query.return_value = [
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 100 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 50 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 150 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 75 * 1024 * 1024]]),
},
]
result = fetch_main_headroom(["static_quality_gate_agent_deb_amd64"])
self.assertEqual(len(result), 1)
self.assertIn("static_quality_gate_agent_deb_amd64", result)
headroom = result["static_quality_gate_agent_deb_amd64"]
# disk_headroom = 150 - 100 = 50 MiB
self.assertEqual(headroom["disk_headroom"], 50 * 1024 * 1024)
# wire_headroom = 75 - 50 = 25 MiB
self.assertEqual(headroom["wire_headroom"], 25 * 1024 * 1024)
@patch("tasks.static_quality_gates.metrics.query_metrics")
def test_headroom_floored_when_main_over_its_own_limit(self, mock_query):
"""Headroom should floor at BUFFER_SIZE, not 0-, when main is over its own limit."""
# Single API call with current > max
mock_query.return_value = [
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 200 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 100 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_disk_size{...}",
"pointlist": make_pointlist([[1704240000, 150 * 1024 * 1024]]),
},
{
"scope": "gate_name:static_quality_gate_agent_deb_amd64",
"expression": "avg:datadog.agent.static_quality_gate.max_allowed_on_wire_size{...}",
"pointlist": make_pointlist([[1704240000, 75 * 1024 * 1024]]),
},
]
result = fetch_main_headroom(["static_quality_gate_agent_deb_amd64"])
headroom = result["static_quality_gate_agent_deb_amd64"]
# disk_headroom = max(BUFFER_SIZE, 150 - 200) = BUFFER_SIZE
self.assertEqual(headroom["disk_headroom"], BUFFER_SIZE)
def test_returns_empty_for_no_gates(self):
"""Should return empty dict when no gates provided."""
result = fetch_main_headroom([])
self.assertEqual(result, {})
class TestGateMetricsData(unittest.TestCase):
"""Test the GateMetricsData dataclass."""
def test_default_values(self):
"""Should have None as default for all fields."""
metrics = GateMetricsData()
self.assertIsNone(metrics.current_on_disk_size)
self.assertIsNone(metrics.current_on_wire_size)
self.assertIsNone(metrics.max_on_disk_size)
self.assertIsNone(metrics.max_on_wire_size)
self.assertIsNone(metrics.relative_on_disk_size)
self.assertIsNone(metrics.relative_on_wire_size)
def test_with_values(self):
"""Should store provided values."""
metrics = GateMetricsData(
current_on_disk_size=100,
current_on_wire_size=50,
max_on_disk_size=150,
max_on_wire_size=75,
relative_on_disk_size=10,
relative_on_wire_size=5,
)
self.assertEqual(metrics.current_on_disk_size, 100)
self.assertEqual(metrics.current_on_wire_size, 50)
self.assertEqual(metrics.max_on_disk_size, 150)
self.assertEqual(metrics.max_on_wire_size, 75)
self.assertEqual(metrics.relative_on_disk_size, 10)
self.assertEqual(metrics.relative_on_wire_size, 5)
if __name__ == '__main__':
unittest.main()