Skip to content

Commit e8805df

Browse files
philjleeclaude
andauthored
[cilium] strengthen tests to lift mutation score (DataDog#23752)
* [cilium] add env-agnostic unit tests to lift mutation score The existing tests/test_cilium.py is gated by requires_new_environment, so it skips entirely under the legacy hatch env and runs only a thin slice under the modern env. This left CiliumCheckV2._parse_config, the legacy CiliumCheck __new__/__init__ routing, and construct_metrics_config's suffix stripping with 0% mutation coverage. Add tests/test_unit.py with 29 env-agnostic tests covering: - construct_metrics_config: _total / _counter suffix stripping, passthrough, empty input, iteration, substring-not-suffix. - CiliumCheckV2._parse_config: DEFAULT_METRIC_LIMIT, missing-endpoint validation, agent-only / operator-only / both scraper construction, agent-vs-operator metric set selection. - Legacy CiliumCheck: __new__ routing on use_openmetrics (truthy / falsy / absent / first-instance), __init__ validation (both / neither endpoint), first-instance endpoint selection, operator vs agent metric set, default and custom prometheus_timeout, namespace, metadata_metric_name. Local cosmic-ray run with --test-path tests/test_unit.py: 51 mutants generated, 51 killed, 0 survived (100% from 0%). * [cilium] Replace repr-based metric-set assertions with structural key iteration Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent b71f79e commit e8805df

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

cilium/tests/test_unit.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
import pytest
5+
6+
from datadog_checks.base import ConfigurationError
7+
from datadog_checks.cilium import CiliumCheck
8+
from datadog_checks.cilium.check import CiliumCheckV2
9+
from datadog_checks.cilium.cilium import CiliumCheck as LegacyCiliumCheck
10+
from datadog_checks.cilium.metrics import construct_metrics_config
11+
12+
pytestmark = pytest.mark.unit
13+
14+
15+
def test_construct_metrics_config_strips_total_suffix():
16+
out = construct_metrics_config({"cilium_drop_count_total": "drop_count.total"})
17+
assert out == [{"cilium_drop_count": {"name": "drop_count"}}]
18+
19+
20+
def test_construct_metrics_config_strips_counter_suffix():
21+
out = construct_metrics_config({"cilium_api_counter": "api.counter"})
22+
assert out == [{"cilium_api": {"name": "api"}}]
23+
24+
25+
def test_construct_metrics_config_passes_through_when_no_suffix():
26+
out = construct_metrics_config({"cilium_endpoint": "endpoint.count"})
27+
assert out == [{"cilium_endpoint": {"name": "endpoint.count"}}]
28+
29+
30+
def test_construct_metrics_config_empty_map_returns_empty_list():
31+
assert construct_metrics_config({}) == []
32+
33+
34+
def test_construct_metrics_config_iterates_every_entry():
35+
out = construct_metrics_config(
36+
{
37+
"a_total": "a.total",
38+
"b_counter": "b.counter",
39+
"c": "c",
40+
}
41+
)
42+
assert len(out) == 3
43+
assert {"a": {"name": "a"}} in out
44+
assert {"b": {"name": "b"}} in out
45+
assert {"c": {"name": "c"}} in out
46+
47+
48+
def test_construct_metrics_config_total_substring_not_stripped():
49+
# "_total" must be a suffix; the substring elsewhere stays intact.
50+
out = construct_metrics_config({"totally_random": "totally.random"})
51+
assert out == [{"totally_random": {"name": "totally.random"}}]
52+
53+
54+
def test_construct_metrics_config_counter_substring_not_stripped():
55+
out = construct_metrics_config({"countermeasure": "countermeasure"})
56+
assert out == [{"countermeasure": {"name": "countermeasure"}}]
57+
58+
59+
def test_v2_default_metric_limit_is_zero():
60+
assert CiliumCheckV2.DEFAULT_METRIC_LIMIT == 0
61+
62+
63+
def test_v2_raises_when_neither_endpoint_set():
64+
check = CiliumCheckV2("cilium", {}, [{}])
65+
with pytest.raises(ConfigurationError, match="Must specify at least one"):
66+
check._parse_config()
67+
68+
69+
def test_v2_agent_endpoint_only_builds_one_scraper_pointing_at_agent():
70+
check = CiliumCheckV2("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
71+
check._parse_config()
72+
assert len(check.scraper_configs) == 1
73+
assert check.scraper_configs[0]["openmetrics_endpoint"] == "http://agent/metrics"
74+
75+
76+
def test_v2_operator_endpoint_only_builds_one_scraper_pointing_at_operator():
77+
check = CiliumCheckV2("cilium", {}, [{"operator_endpoint": "http://op/metrics"}])
78+
check._parse_config()
79+
assert len(check.scraper_configs) == 1
80+
assert check.scraper_configs[0]["openmetrics_endpoint"] == "http://op/metrics"
81+
82+
83+
def test_v2_both_endpoints_build_two_scrapers():
84+
check = CiliumCheckV2(
85+
"cilium",
86+
{},
87+
[{"agent_endpoint": "http://agent/metrics", "operator_endpoint": "http://op/metrics"}],
88+
)
89+
check._parse_config()
90+
endpoints = sorted(s["openmetrics_endpoint"] for s in check.scraper_configs)
91+
assert endpoints == ["http://agent/metrics", "http://op/metrics"]
92+
93+
94+
def test_v2_agent_scraper_uses_agent_metrics_not_operator():
95+
check = CiliumCheckV2("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
96+
check._parse_config()
97+
metric_keys = {next(iter(m)) for m in check.scraper_configs[0]["metrics"]}
98+
assert "cilium_drop_count" in metric_keys
99+
assert not any(name.startswith("cilium_operator_eni_") for name in metric_keys)
100+
101+
102+
def test_v2_operator_scraper_uses_operator_metrics_not_agent():
103+
check = CiliumCheckV2("cilium", {}, [{"operator_endpoint": "http://op/metrics"}])
104+
check._parse_config()
105+
metric_keys = {next(iter(m)) for m in check.scraper_configs[0]["metrics"]}
106+
assert any(name.startswith("cilium_operator_") for name in metric_keys)
107+
assert "cilium_drop_count" not in metric_keys
108+
109+
110+
def test_legacy_default_metric_limit_is_zero():
111+
assert LegacyCiliumCheck.DEFAULT_METRIC_LIMIT == 0
112+
113+
114+
def test_legacy_routes_to_v2_when_use_openmetrics_is_truthy():
115+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics", "use_openmetrics": True}])
116+
assert isinstance(check, CiliumCheckV2)
117+
118+
119+
def test_legacy_routes_to_legacy_when_use_openmetrics_is_falsy():
120+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics", "use_openmetrics": False}])
121+
assert not isinstance(check, CiliumCheckV2)
122+
123+
124+
def test_legacy_default_use_openmetrics_routes_to_legacy():
125+
# When the key is absent, the get() default must be False so we stay on legacy.
126+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
127+
assert not isinstance(check, CiliumCheckV2)
128+
129+
130+
def test_legacy_new_picks_first_instance_for_routing_legacy_path():
131+
check = CiliumCheck(
132+
"cilium",
133+
{},
134+
[
135+
{"agent_endpoint": "http://agent/metrics", "use_openmetrics": False},
136+
{"agent_endpoint": "http://agent2/metrics", "use_openmetrics": True},
137+
],
138+
)
139+
assert not isinstance(check, CiliumCheckV2)
140+
141+
142+
def test_legacy_new_picks_first_instance_for_routing_v2_path():
143+
check = CiliumCheck(
144+
"cilium",
145+
{},
146+
[
147+
{"agent_endpoint": "http://agent/metrics", "use_openmetrics": True},
148+
{"agent_endpoint": "http://agent2/metrics", "use_openmetrics": False},
149+
],
150+
)
151+
assert isinstance(check, CiliumCheckV2)
152+
153+
154+
def test_legacy_raises_when_both_endpoints_set():
155+
with pytest.raises(ConfigurationError, match="Only one endpoint needs to be specified"):
156+
CiliumCheck(
157+
"cilium",
158+
{},
159+
[{"agent_endpoint": "http://agent/metrics", "operator_endpoint": "http://op/metrics"}],
160+
)
161+
162+
163+
def test_legacy_raises_when_neither_endpoint_set():
164+
with pytest.raises(ConfigurationError, match="Must provide at least one endpoint"):
165+
CiliumCheck("cilium", {}, [{}])
166+
167+
168+
def test_legacy_init_uses_first_instance_for_endpoint_choice():
169+
check = CiliumCheck(
170+
"cilium",
171+
{},
172+
[
173+
{"agent_endpoint": "http://first/metrics"},
174+
{"agent_endpoint": "http://second/metrics"},
175+
],
176+
)
177+
assert check.instance["prometheus_url"] == "http://first/metrics"
178+
179+
180+
def test_legacy_operator_endpoint_selects_operator_metric_set():
181+
check = CiliumCheck("cilium", {}, [{"operator_endpoint": "http://op/metrics"}])
182+
assert check.instance["prometheus_url"] == "http://op/metrics"
183+
metric_keys = {key for m in check.instance["metrics"] for key in m}
184+
assert "cilium_operator_process_cpu_seconds_total" in metric_keys
185+
186+
187+
def test_legacy_agent_endpoint_selects_agent_metric_set():
188+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
189+
assert check.instance["prometheus_url"] == "http://agent/metrics"
190+
metric_keys = {key for m in check.instance["metrics"] for key in m}
191+
assert "cilium_drop_count_total" in metric_keys
192+
assert "cilium_operator_eni_available" not in metric_keys
193+
194+
195+
def test_legacy_default_prometheus_timeout_is_10():
196+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
197+
assert check.instance["prometheus_timeout"] == 10
198+
199+
200+
def test_legacy_custom_timeout_passes_through():
201+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics", "timeout": 42}])
202+
assert check.instance["prometheus_timeout"] == 42
203+
204+
205+
def test_legacy_namespace_is_cilium():
206+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
207+
assert check.instance["namespace"] == "cilium"
208+
209+
210+
def test_legacy_metadata_metric_name_is_cilium_version():
211+
check = CiliumCheck("cilium", {}, [{"agent_endpoint": "http://agent/metrics"}])
212+
assert check.instance["metadata_metric_name"] == "cilium_version"

0 commit comments

Comments
 (0)