Skip to content

Commit df6e9d6

Browse files
committed
network ethtool unit tests
1 parent f46f478 commit df6e9d6

1 file changed

Lines changed: 284 additions & 0 deletions

File tree

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2026 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
import pytest
27+
28+
from nodescraper.enums import EventPriority, ExecutionStatus
29+
from nodescraper.plugins.inband.network.network_analyzer import NetworkAnalyzer
30+
from nodescraper.plugins.inband.network.networkdata import (
31+
EthtoolInfo,
32+
NetworkDataModel,
33+
)
34+
35+
36+
@pytest.fixture
37+
def network_analyzer(system_info):
38+
return NetworkAnalyzer(system_info)
39+
40+
41+
@pytest.fixture
42+
def clean_ethtool_info():
43+
"""EthtoolInfo with no errors (all counters zero)."""
44+
return EthtoolInfo(
45+
interface="eth0",
46+
raw_output="dummy output",
47+
statistics={
48+
"tx_pfc_frames": "0",
49+
"tx_pfc_ena_frames_pri0": "0",
50+
"tx_pfc_ena_frames_pri1": "0",
51+
"tx_pfc_ena_frames_pri2": "0",
52+
"tx_pfc_ena_frames_pri3": "0",
53+
"tx_pfc_ena_frames_pri4": "0",
54+
"tx_pfc_ena_frames_pri5": "0",
55+
"tx_pfc_ena_frames_pri6": "0",
56+
"tx_pfc_ena_frames_pri7": "0",
57+
"pfc_pri0_tx_transitions": "0",
58+
"pfc_pri1_tx_transitions": "0",
59+
"pfc_pri2_tx_transitions": "0",
60+
"pfc_pri3_tx_transitions": "0",
61+
"pfc_pri4_tx_transitions": "0",
62+
"pfc_pri5_tx_transitions": "0",
63+
"pfc_pri6_tx_transitions": "0",
64+
"pfc_pri7_tx_transitions": "0",
65+
"some_other_stat": "100", # Should be ignored
66+
"rx_bytes": "1234567", # Should be ignored
67+
},
68+
)
69+
70+
71+
@pytest.fixture
72+
def clean_network_model(clean_ethtool_info):
73+
"""Network data with no errors (all counters zero)."""
74+
return NetworkDataModel(
75+
ethtool_info={
76+
"eth0": clean_ethtool_info,
77+
}
78+
)
79+
80+
81+
def test_no_errors_detected(network_analyzer, clean_network_model):
82+
"""Test with nominal data that has no errors."""
83+
result = network_analyzer.analyze_data(clean_network_model)
84+
assert result.status == ExecutionStatus.OK
85+
assert "No network errors detected" in result.message
86+
assert len(result.events) == 0
87+
88+
89+
def test_single_error_detected(network_analyzer, clean_ethtool_info):
90+
"""Test with data containing a single error."""
91+
clean_ethtool_info.statistics["tx_pfc_frames"] = "5"
92+
model = NetworkDataModel(ethtool_info={"eth0": clean_ethtool_info})
93+
result = network_analyzer.analyze_data(model)
94+
assert result.status == ExecutionStatus.ERROR
95+
assert "Network errors detected in statistics" in result.message
96+
assert len(result.events) == 1
97+
assert result.events[0].description == "Network error detected on eth0: [tx_pfc_frames]"
98+
assert result.events[0].priority == EventPriority.ERROR
99+
assert result.events[0].data["errors"] == {"tx_pfc_frames": 5}
100+
assert result.events[0].data["interface"] == "eth0"
101+
102+
103+
def test_multiple_errors_same_interface(network_analyzer, clean_ethtool_info):
104+
"""Test with data containing multiple errors on the same interface."""
105+
clean_ethtool_info.statistics["tx_pfc_frames"] = "10"
106+
clean_ethtool_info.statistics["tx_pfc_ena_frames_pri0"] = "3"
107+
clean_ethtool_info.statistics["pfc_pri2_tx_transitions"] = "7"
108+
model = NetworkDataModel(ethtool_info={"eth0": clean_ethtool_info})
109+
result = network_analyzer.analyze_data(model)
110+
assert result.status == ExecutionStatus.ERROR
111+
assert "Network errors detected in statistics" in result.message
112+
assert len(result.events) == 1 # one event per interface
113+
assert result.events[0].priority == EventPriority.ERROR
114+
# Check all 3 errors are present
115+
assert len(result.events[0].data["errors"]) == 3
116+
assert result.events[0].data["errors"]["tx_pfc_frames"] == 10
117+
assert result.events[0].data["errors"]["tx_pfc_ena_frames_pri0"] == 3
118+
assert result.events[0].data["errors"]["pfc_pri2_tx_transitions"] == 7
119+
120+
121+
def test_multiple_interfaces_with_errors(network_analyzer):
122+
"""Test with errors across multiple interfaces."""
123+
eth0 = EthtoolInfo(
124+
interface="eth0",
125+
raw_output="dummy",
126+
statistics={
127+
"tx_pfc_frames": "15",
128+
"tx_pfc_ena_frames_pri1": "0",
129+
},
130+
)
131+
eth1 = EthtoolInfo(
132+
interface="eth1",
133+
raw_output="dummy",
134+
statistics={
135+
"pfc_pri3_tx_transitions": "8",
136+
},
137+
)
138+
eth2 = EthtoolInfo(
139+
interface="eth2",
140+
raw_output="dummy",
141+
statistics={
142+
"tx_pfc_ena_frames_pri7": "100",
143+
},
144+
)
145+
model = NetworkDataModel(
146+
ethtool_info={
147+
"eth0": eth0,
148+
"eth1": eth1,
149+
"eth2": eth2,
150+
}
151+
)
152+
result = network_analyzer.analyze_data(model)
153+
assert result.status == ExecutionStatus.ERROR
154+
assert len(result.events) == 3
155+
interfaces = {event.data["interface"] for event in result.events}
156+
assert interfaces == {"eth0", "eth1", "eth2"}
157+
158+
159+
def test_empty_ethtool_info(network_analyzer):
160+
"""Test with empty ethtool_info: WARNING and message logged."""
161+
model = NetworkDataModel(ethtool_info={})
162+
result = network_analyzer.analyze_data(model)
163+
assert result.status == ExecutionStatus.WARNING
164+
assert result.message == "No network devices found"
165+
166+
167+
def test_regex_patterns_priority_numbers(network_analyzer):
168+
"""Test that regex patterns match various priority numbers (0-7 and beyond)."""
169+
ethtool = EthtoolInfo(
170+
interface="eth0",
171+
raw_output="dummy",
172+
statistics={
173+
"tx_pfc_ena_frames_pri0": "1",
174+
"tx_pfc_ena_frames_pri3": "2",
175+
"tx_pfc_ena_frames_pri7": "3",
176+
"tx_pfc_ena_frames_pri10": "4", # Test double-digit
177+
"pfc_pri0_tx_transitions": "5",
178+
"pfc_pri5_tx_transitions": "6",
179+
"pfc_pri15_tx_transitions": "7", # Test double-digit
180+
},
181+
)
182+
model = NetworkDataModel(ethtool_info={"eth0": ethtool})
183+
result = network_analyzer.analyze_data(model)
184+
assert result.status == ExecutionStatus.ERROR
185+
assert len(result.events) == 1
186+
# All 7 errors should be detected
187+
assert len(result.events[0].data["errors"]) == 7
188+
189+
190+
def test_non_numeric_values_ignored(network_analyzer):
191+
"""Test that non-numeric values in statistics are gracefully ignored."""
192+
ethtool = EthtoolInfo(
193+
interface="eth0",
194+
raw_output="dummy",
195+
statistics={
196+
"tx_pfc_frames": "N/A", # Non-numeric
197+
"tx_pfc_ena_frames_pri0": "invalid", # Non-numeric
198+
"pfc_pri1_tx_transitions": "5", # Valid error
199+
},
200+
)
201+
model = NetworkDataModel(ethtool_info={"eth0": ethtool})
202+
result = network_analyzer.analyze_data(model)
203+
assert result.status == ExecutionStatus.ERROR
204+
assert len(result.events) == 1
205+
# Only the valid numeric error should be reported
206+
assert len(result.events[0].data["errors"]) == 1
207+
assert result.events[0].data["errors"]["pfc_pri1_tx_transitions"] == 5
208+
209+
210+
def test_zero_values_not_reported(network_analyzer):
211+
"""Test that zero values are not reported as errors."""
212+
ethtool = EthtoolInfo(
213+
interface="eth0",
214+
raw_output="dummy",
215+
statistics={
216+
"tx_pfc_frames": "0",
217+
"tx_pfc_ena_frames_pri0": "0",
218+
"pfc_pri1_tx_transitions": "0",
219+
},
220+
)
221+
model = NetworkDataModel(ethtool_info={"eth0": ethtool})
222+
result = network_analyzer.analyze_data(model)
223+
assert result.status == ExecutionStatus.OK
224+
assert len(result.events) == 0
225+
226+
227+
def test_non_matching_fields_ignored(network_analyzer):
228+
"""Test that statistics not matching error patterns are ignored."""
229+
ethtool = EthtoolInfo(
230+
interface="eth0",
231+
raw_output="dummy",
232+
statistics={
233+
"rx_bytes": "999999999", # High value but not an error field
234+
"tx_bytes": "888888888", # High value but not an error field
235+
"some_random_counter": "12345", # Not an error field
236+
"tx_pfc_frames": "5", # This SHOULD be detected
237+
},
238+
)
239+
model = NetworkDataModel(ethtool_info={"eth0": ethtool})
240+
result = network_analyzer.analyze_data(model)
241+
assert result.status == ExecutionStatus.ERROR
242+
assert len(result.events) == 1
243+
# Only tx_pfc_frames should be reported
244+
assert len(result.events[0].data["errors"]) == 1
245+
assert "tx_pfc_frames" in result.events[0].data["errors"]
246+
247+
248+
def test_mixed_interfaces_with_and_without_errors(network_analyzer):
249+
"""Test with some interfaces having errors and others clean."""
250+
eth0_error = EthtoolInfo(
251+
interface="eth0",
252+
raw_output="dummy",
253+
statistics={
254+
"tx_pfc_frames": "10",
255+
},
256+
)
257+
eth1_clean = EthtoolInfo(
258+
interface="eth1",
259+
raw_output="dummy",
260+
statistics={
261+
"tx_pfc_frames": "0",
262+
"tx_pfc_ena_frames_pri0": "0",
263+
},
264+
)
265+
eth2_error = EthtoolInfo(
266+
interface="eth2",
267+
raw_output="dummy",
268+
statistics={
269+
"pfc_pri5_tx_transitions": "20",
270+
},
271+
)
272+
model = NetworkDataModel(
273+
ethtool_info={
274+
"eth0": eth0_error,
275+
"eth1": eth1_clean,
276+
"eth2": eth2_error,
277+
}
278+
)
279+
result = network_analyzer.analyze_data(model)
280+
assert result.status == ExecutionStatus.ERROR
281+
# Only 2 events (eth0 and eth2), eth1 should not generate an event
282+
assert len(result.events) == 2
283+
interfaces_with_errors = {event.data["interface"] for event in result.events}
284+
assert interfaces_with_errors == {"eth0", "eth2"}

0 commit comments

Comments
 (0)