@@ -59,136 +59,148 @@ class IfAttributesMock:
5959
6060 return device
6161
62+ def _test_parse_device_info (self , fixture_name ):
63+ """
64+ Helper method to test parse_device_info() for a single JSON fixture.
65+ """
66+ data = load_json_fixture (fixture_name )
67+ device_info = data ["device_info" ]
68+
69+ device = self .create_mock_device_from_json (device_info )
70+ metrics = parse_device_info (device )
71+
72+ dev_name = device_info ["name" ]
73+ dev_iface = device_info ["interface" ]
74+ dev_serial = device_info ["serial" ].lower ()
75+
76+ # The device_info line should exist for every device
77+ # e.g. device_info{disk="/dev/...",type="...",serial_number="..."} 1
78+ device_info_found = any (
79+ line .startswith ("device_info{" ) and
80+ f'disk="{ dev_name } "' in line and
81+ f'type="{ dev_iface } "' in line and
82+ f'serial_number="{ dev_serial } "' in line
83+ for line in metrics
84+ )
85+ self .assertTrue (
86+ device_info_found ,
87+ f"Expected a device_info metric line for { dev_name } but didn't find it."
88+ )
89+
90+ # If smart_capable is true, we expect device_smart_available = 1
91+ if device_info .get ("smart_capable" ):
92+ smart_available_found = any (
93+ line .startswith ("device_smart_available{" ) and
94+ f'disk="{ dev_name } "' in line and
95+ f'serial_number="{ dev_serial } "' in line and
96+ line .endswith (" 1" )
97+ for line in metrics
98+ )
99+ self .assertTrue (
100+ smart_available_found ,
101+ f"Expected device_smart_available=1 for { dev_name } , not found."
102+ )
103+
104+ # If smart_enabled is true, we expect device_smart_enabled = 1
105+ if device_info .get ("smart_enabled" ):
106+ smart_enabled_found = any (
107+ line .startswith ("device_smart_enabled{" ) and
108+ f'disk="{ dev_name } "' in line and
109+ line .endswith (" 1" )
110+ for line in metrics
111+ )
112+ self .assertTrue (
113+ smart_enabled_found ,
114+ f"Expected device_smart_enabled=1 for { dev_name } , not found."
115+ )
116+
117+ # device_smart_healthy if assessment in [PASS, WARN, FAIL]
118+ # PASS => 1, otherwise => 0
119+ assessment = device_info .get ("assessment" , "" ).upper ()
120+ if assessment in ["PASS" , "WARN" , "FAIL" ]:
121+ expected_val = 1 if assessment == "PASS" else 0
122+ smart_healthy_found = any (
123+ line .startswith ("device_smart_healthy{" ) and
124+ f'disk="{ dev_name } "' in line and
125+ line .endswith (f" { expected_val } " )
126+ for line in metrics
127+ )
128+ self .assertTrue (
129+ smart_healthy_found ,
130+ f"Expected device_smart_healthy={ expected_val } for { dev_name } , not found."
131+ )
132+
62133 def test_parse_device_info (self ):
63134 """
64135 Test parse_device_info() for every JSON fixture in ./drives/.
65- We do subTest() so each fixture is tested individually.
136+ Each fixture is tested individually with clear error reporting .
66137 """
67138 for fixture_path in self .fixture_files :
68139 fixture_name = os .path .basename (fixture_path )
69- with self .subTest (msg = f"Testing device_info with { fixture_name } " ):
70- data = load_json_fixture (fixture_name )
71- device_info = data ["device_info" ]
72-
73- device = self .create_mock_device_from_json (device_info )
74- metrics = parse_device_info (device )
140+ with self .subTest (fixture = fixture_name ):
141+ self ._test_parse_device_info (fixture_name )
75142
76- dev_name = device_info ["name" ]
77- dev_iface = device_info ["interface" ]
78- dev_serial = device_info ["serial" ].lower ()
79-
80- # The device_info line should exist for every device
81- # e.g. device_info{disk="/dev/...",type="...",serial_number="..."} 1
82- device_info_found = any (
83- line .startswith ("device_info{" ) and
84- f'disk="{ dev_name } "' in line and
85- f'type="{ dev_iface } "' in line and
86- f'serial_number="{ dev_serial } "' in line
87- for line in metrics
143+ def _test_parse_if_attributes (self , fixture_name ):
144+ """
145+ Helper method to test parse_if_attributes() for a single JSON fixture.
146+ """
147+ data = load_json_fixture (fixture_name )
148+ device_info = data ["device_info" ]
149+ if_attrs = data .get ("if_attributes" , {})
150+
151+ device = self .create_mock_device_from_json (device_info , if_attrs )
152+ metrics = parse_if_attributes (device )
153+
154+ dev_name = device_info ["name" ]
155+ dev_iface = device_info ["interface" ]
156+ dev_serial = device_info ["serial" ].lower ()
157+
158+ # For each numeric attribute in JSON, if it's in SMARTMON_ATTRS,
159+ # we expect a line in the script's output.
160+ for attr_key , attr_val in if_attrs .items ():
161+ # Convert from e.g. "criticalWarning" -> "critical_warning"
162+ snake_key = re .sub (r'(?<!^)(?=[A-Z])' , '_' , attr_key ).lower ()
163+
164+ if isinstance (attr_val , (int , float )) and snake_key in SMARTMON_ATTRS :
165+ # We expect e.g. critical_warning{disk="/dev/..."} <value>
166+ expected_line = (
167+ f"{ snake_key } {{disk=\" { dev_name } \" ,type=\" { dev_iface } \" ,serial_number=\" { dev_serial } \" }} { attr_val } "
88168 )
89- self .assertTrue (
90- device_info_found ,
91- f"Expected a device_info metric line for { dev_name } but didn't find it."
169+ self .assertIn (
170+ expected_line ,
171+ metrics ,
172+ f"Expected metric '{ expected_line } ' for attribute '{ attr_key } ' not found."
173+ )
174+ else :
175+ # If it's not in SMARTMON_ATTRS or not numeric,
176+ # we do NOT expect a line with that name+value
177+ unexpected_line = (
178+ f"{ snake_key } {{disk=\" { dev_name } \" ,type=\" { dev_iface } \" ,serial_number=\" { dev_serial } \" }} { attr_val } "
179+ )
180+ self .assertNotIn (
181+ unexpected_line ,
182+ metrics ,
183+ f"Unexpected metric '{ unexpected_line } ' found for { attr_key } ."
92184 )
93185
94- # If smart_capable is true, we expect device_smart_available = 1
95- if device_info .get ("smart_capable" ):
96- smart_available_found = any (
97- line .startswith ("device_smart_available{" ) and
98- f'disk="{ dev_name } "' in line and
99- f'serial_number="{ dev_serial } "' in line and
100- line .endswith (" 1" )
101- for line in metrics
102- )
103- self .assertTrue (
104- smart_available_found ,
105- f"Expected device_smart_available=1 for { dev_name } , not found."
106- )
107-
108- # If smart_enabled is true, we expect device_smart_enabled = 1
109- if device_info .get ("smart_enabled" ):
110- smart_enabled_found = any (
111- line .startswith ("device_smart_enabled{" ) and
112- f'disk="{ dev_name } "' in line and
113- line .endswith (" 1" )
114- for line in metrics
115- )
116- self .assertTrue (
117- smart_enabled_found ,
118- f"Expected device_smart_enabled=1 for { dev_name } , not found."
119- )
120-
121- # device_smart_healthy if assessment in [PASS, WARN, FAIL]
122- # PASS => 1, otherwise => 0
123- assessment = device_info .get ("assessment" , "" ).upper ()
124- if assessment in ["PASS" , "WARN" , "FAIL" ]:
125- expected_val = 1 if assessment == "PASS" else 0
126- smart_healthy_found = any (
127- line .startswith ("device_smart_healthy{" ) and
128- f'disk="{ dev_name } "' in line and
129- line .endswith (f" { expected_val } " )
130- for line in metrics
131- )
132- self .assertTrue (
133- smart_healthy_found ,
134- f"Expected device_smart_healthy={ expected_val } for { dev_name } , not found."
135- )
186+ # Also ensure that non-numeric or disallowed attributes do not appear
187+ # For instance "notInSmartmonAttrs" should never appear.
188+ for line in metrics :
189+ self .assertNotIn (
190+ "not_in_smartmon_attrs" ,
191+ line ,
192+ f"'notInSmartmonAttrs' attribute unexpectedly found in metric line: { line } "
193+ )
136194
137195 def test_parse_if_attributes (self ):
138196 """
139197 Test parse_if_attributes() for every JSON fixture in ./drives/.
140- We do subTest() so each fixture is tested individually.
198+ Each fixture is tested individually with clear error reporting .
141199 """
142200 for fixture_path in self .fixture_files :
143201 fixture_name = os .path .basename (fixture_path )
144- with self .subTest (msg = f"Testing if_attributes with { fixture_name } " ):
145- data = load_json_fixture (fixture_name )
146- device_info = data ["device_info" ]
147- if_attrs = data .get ("if_attributes" , {})
148-
149- device = self .create_mock_device_from_json (device_info , if_attrs )
150- metrics = parse_if_attributes (device )
151-
152- dev_name = device_info ["name" ]
153- dev_iface = device_info ["interface" ]
154- dev_serial = device_info ["serial" ].lower ()
155-
156- # For each numeric attribute in JSON, if it's in SMARTMON_ATTRS,
157- # we expect a line in the script's output.
158- for attr_key , attr_val in if_attrs .items ():
159- # Convert from e.g. "criticalWarning" -> "critical_warning"
160- snake_key = re .sub (r'(?<!^)(?=[A-Z])' , '_' , attr_key ).lower ()
161-
162- if isinstance (attr_val , (int , float )) and snake_key in SMARTMON_ATTRS :
163- # We expect e.g. critical_warning{disk="/dev/..."} <value>
164- expected_line = (
165- f"{ snake_key } {{disk=\" { dev_name } \" ,type=\" { dev_iface } \" ,serial_number=\" { dev_serial } \" }} { attr_val } "
166- )
167- self .assertIn (
168- expected_line ,
169- metrics ,
170- f"Expected metric '{ expected_line } ' for attribute '{ attr_key } ' not found."
171- )
172- else :
173- # If it's not in SMARTMON_ATTRS or not numeric,
174- # we do NOT expect a line with that name+value
175- unexpected_line = (
176- f"{ snake_key } {{disk=\" { dev_name } \" ,type=\" { dev_iface } \" ,serial_number=\" { dev_serial } \" }} { attr_val } "
177- )
178- self .assertNotIn (
179- unexpected_line ,
180- metrics ,
181- f"Unexpected metric '{ unexpected_line } ' found for { attr_key } ."
182- )
183-
184- # Also ensure that non-numeric or disallowed attributes do not appear
185- # For instance "notInSmartmonAttrs" should never appear.
186- for line in metrics :
187- self .assertNotIn (
188- "not_in_smartmon_attrs" ,
189- line ,
190- f"'notInSmartmonAttrs' attribute unexpectedly found in metric line: { line } "
191- )
202+ with self .subTest (fixture = fixture_name ):
203+ self ._test_parse_if_attributes (fixture_name )
192204
193205 @patch ("smartmon.run_command" )
194206 @patch ("smartmon.DeviceList" )
0 commit comments