amd-debug-tools 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amd_debug/common.py +48 -0
- amd_debug/display.py +34 -0
- amd_debug/failures.py +13 -1
- amd_debug/installer.py +69 -5
- amd_debug/prerequisites.py +157 -56
- amd_debug/s2idle.py +46 -17
- amd_debug/sleep_report.py +2 -2
- amd_debug/templates/md +0 -7
- amd_debug/validator.py +3 -5
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/METADATA +4 -3
- amd_debug_tools-0.2.2.dist-info/RECORD +45 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/WHEEL +1 -1
- amd_debug_tools-0.2.2.dist-info/top_level.txt +18 -0
- launcher.py +35 -0
- test_acpi.py +90 -0
- test_batteries.py +92 -0
- test_bios.py +250 -0
- test_common.py +444 -0
- test_database.py +284 -0
- test_display.py +143 -0
- test_failures.py +146 -0
- test_installer.py +281 -0
- test_kernel.py +205 -0
- test_launcher.py +53 -0
- test_prerequisites.py +1935 -0
- test_pstate.py +164 -0
- test_s2idle.py +868 -0
- test_sleep_report.py +167 -0
- test_validator.py +723 -0
- test_wake.py +216 -0
- amd_debug_tools-0.2.0.dist-info/RECORD +0 -27
- amd_debug_tools-0.2.0.dist-info/top_level.txt +0 -1
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/entry_points.txt +0 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/licenses/LICENSE +0 -0
test_validator.py
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
This module contains unit tests for the validator functions in the amd-debug-tools package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from unittest.mock import patch, mock_open
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import unittest
|
|
12
|
+
import math
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
from amd_debug.validator import pm_debugging, soc_needs_irq1_wa, SleepValidator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestValidatorHelpers(unittest.TestCase):
|
|
19
|
+
"""Test validator Helper functions"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def setUpClass(cls):
|
|
23
|
+
logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
|
|
24
|
+
|
|
25
|
+
def test_soc_needs_irq1_wa(self):
|
|
26
|
+
"""Test if the SOC should apply an IRQ1 workaround"""
|
|
27
|
+
ret = soc_needs_irq1_wa(0x17, 0x68, "1.2.3")
|
|
28
|
+
self.assertTrue(ret)
|
|
29
|
+
ret = soc_needs_irq1_wa(0x17, 0x69, "1.2.3")
|
|
30
|
+
self.assertFalse(ret)
|
|
31
|
+
ret = soc_needs_irq1_wa(0x19, 0x51, "64.65.0")
|
|
32
|
+
self.assertFalse(ret)
|
|
33
|
+
ret = soc_needs_irq1_wa(0x19, 0x50, "64.65.0")
|
|
34
|
+
self.assertTrue(ret)
|
|
35
|
+
ret = soc_needs_irq1_wa(0x19, 0x50, "64.66.0")
|
|
36
|
+
self.assertFalse(ret)
|
|
37
|
+
|
|
38
|
+
def test_pm_debugging(self):
|
|
39
|
+
"""Test pm_debugging decorator"""
|
|
40
|
+
|
|
41
|
+
@pm_debugging
|
|
42
|
+
def test_function():
|
|
43
|
+
return "Test function executed"
|
|
44
|
+
|
|
45
|
+
# Mock /sys/power/pm_debug_messages existing and all ACPI existing
|
|
46
|
+
with patch("builtins.open", new_callable=mock_open, read_data="0") as mock_file:
|
|
47
|
+
handlers = (
|
|
48
|
+
mock_file.return_value,
|
|
49
|
+
mock_open(read_data="0").return_value,
|
|
50
|
+
mock_open(read_data="0").return_value,
|
|
51
|
+
mock_open(read_data="0").return_value,
|
|
52
|
+
)
|
|
53
|
+
mock_open.side_effect = handlers
|
|
54
|
+
result = test_function()
|
|
55
|
+
self.assertEqual(result, "Test function executed")
|
|
56
|
+
|
|
57
|
+
# Mock /sys/power/pm_debug_messages missing
|
|
58
|
+
with patch(
|
|
59
|
+
"builtins.open", side_effect=FileNotFoundError("not found")
|
|
60
|
+
) as mock_file:
|
|
61
|
+
with self.assertRaises(FileNotFoundError):
|
|
62
|
+
result = test_function()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestValidator(unittest.TestCase):
|
|
66
|
+
"""Test validator functions"""
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def setUpClass(cls):
|
|
70
|
+
logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
|
|
71
|
+
|
|
72
|
+
@patch("amd_debug.validator.SleepDatabase")
|
|
73
|
+
def setUp(self, _db_mock):
|
|
74
|
+
"""Set up a mock context for testing"""
|
|
75
|
+
self.validator = SleepValidator(tool_debug=True, bios_debug=False)
|
|
76
|
+
|
|
77
|
+
def test_capture_running_compositors(self):
|
|
78
|
+
"""Test capture_running_compositors method"""
|
|
79
|
+
with patch("glob.glob", return_value=["/proc/1234", "/proc/5678"]), patch(
|
|
80
|
+
"os.path.exists", return_value=True
|
|
81
|
+
), patch(
|
|
82
|
+
"os.readlink", side_effect=["/usr/bin/kwin_wayland", "/usr/bin/gnome-shell"]
|
|
83
|
+
), patch.object(
|
|
84
|
+
self.validator.db, "record_debug"
|
|
85
|
+
) as mock_record_debug:
|
|
86
|
+
self.validator.capture_running_compositors()
|
|
87
|
+
mock_record_debug.assert_any_call("kwin_wayland compositor is running")
|
|
88
|
+
mock_record_debug.assert_any_call("gnome-shell compositor is running")
|
|
89
|
+
|
|
90
|
+
def test_capture_power_profile(self):
|
|
91
|
+
"""Test capture_power_profile method"""
|
|
92
|
+
with patch("os.path.exists", return_value=True), patch(
|
|
93
|
+
"subprocess.check_output",
|
|
94
|
+
return_value=b"Performance\nBalanced\nPower Saver",
|
|
95
|
+
), patch.object(self.validator.db, "record_debug") as mock_record_debug:
|
|
96
|
+
self.validator.capture_power_profile()
|
|
97
|
+
mock_record_debug.assert_any_call("Power Profiles:")
|
|
98
|
+
mock_record_debug.assert_any_call("│ Performance")
|
|
99
|
+
mock_record_debug.assert_any_call("│ Balanced")
|
|
100
|
+
mock_record_debug.assert_any_call("└─Power Saver")
|
|
101
|
+
|
|
102
|
+
def test_capture_battery(self):
|
|
103
|
+
"""Test capture_battery method"""
|
|
104
|
+
with patch.object(
|
|
105
|
+
self.validator.batteries, "get_batteries", return_value=["BAT0"]
|
|
106
|
+
), patch.object(
|
|
107
|
+
self.validator.batteries, "get_energy_unit", return_value="µWh"
|
|
108
|
+
), patch.object(
|
|
109
|
+
self.validator.batteries, "get_energy", return_value=50000
|
|
110
|
+
), patch.object(
|
|
111
|
+
self.validator.batteries, "get_energy_full", return_value=60000
|
|
112
|
+
), patch.object(
|
|
113
|
+
self.validator.db, "record_debug"
|
|
114
|
+
) as mock_record_debug, patch.object(
|
|
115
|
+
self.validator.db, "record_battery_energy"
|
|
116
|
+
) as mock_record_battery_energy:
|
|
117
|
+
self.validator.capture_battery()
|
|
118
|
+
mock_record_debug.assert_called_with("BAT0 energy level is 50000 µWh")
|
|
119
|
+
mock_record_battery_energy.assert_called_with("BAT0", 50000, 60000, "W")
|
|
120
|
+
|
|
121
|
+
def test_check_rtc_cmos(self):
|
|
122
|
+
"""Test check_rtc_cmos method"""
|
|
123
|
+
with patch(
|
|
124
|
+
"os.path.join",
|
|
125
|
+
return_value="/sys/module/rtc_cmos/parameters/use_acpi_alarm",
|
|
126
|
+
), patch("builtins.open", mock_open(read_data="N")), patch.object(
|
|
127
|
+
self.validator.db, "record_cycle_data"
|
|
128
|
+
) as mock_record_cycle_data:
|
|
129
|
+
self.validator.check_rtc_cmos()
|
|
130
|
+
mock_record_cycle_data.assert_called_with(
|
|
131
|
+
"`rtc_cmos` not configured to use ACPI alarm", "🚦"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def test_capture_wake_sources(self):
|
|
135
|
+
"""Test capture_wake_sources method"""
|
|
136
|
+
mock_pyudev = patch.object(self.validator, "pyudev").start()
|
|
137
|
+
mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
138
|
+
mock_read_file = patch("amd_debug.validator.read_file").start()
|
|
139
|
+
mock_os_path_exists = patch("os.path.exists").start()
|
|
140
|
+
|
|
141
|
+
# Mock wakeup devices
|
|
142
|
+
mock_wakeup_device = mock_pyudev.list_devices.return_value = [
|
|
143
|
+
unittest.mock.Mock(
|
|
144
|
+
sys_path="/sys/devices/pci0000:00/0000:00:14.0",
|
|
145
|
+
find_parent=lambda subsystem, **kwargs: None,
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
# Mock wakeup file existence and content
|
|
150
|
+
mock_os_path_exists.return_value = True
|
|
151
|
+
mock_read_file.return_value = "enabled"
|
|
152
|
+
|
|
153
|
+
# Mock device properties
|
|
154
|
+
mock_wakeup_device[0].properties = {"PCI_CLASS": "0x0c0330"}
|
|
155
|
+
mock_wakeup_device[0].sys_path = "/sys/devices/pci0000:00/0000:00:14.0"
|
|
156
|
+
|
|
157
|
+
self.validator.capture_wake_sources()
|
|
158
|
+
|
|
159
|
+
# Validate debug messages
|
|
160
|
+
mock_record_debug.assert_any_call("Possible wakeup sources:")
|
|
161
|
+
mock_record_debug.assert_any_call(
|
|
162
|
+
"└─ [/sys/devices/pci0000:00/0000:00:14.0]: enabled"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Stop patches
|
|
166
|
+
patch.stopall()
|
|
167
|
+
|
|
168
|
+
def test_capture_lid(self):
|
|
169
|
+
"""Test capture_lid method"""
|
|
170
|
+
with patch("os.walk", return_value=[("/", [], ["lid0", "lid1"])]), patch(
|
|
171
|
+
"os.path.join", side_effect=lambda *args: "/".join(args)
|
|
172
|
+
), patch(
|
|
173
|
+
"amd_debug.validator.read_file",
|
|
174
|
+
side_effect=["state: open", "state: closed"],
|
|
175
|
+
), patch.object(
|
|
176
|
+
self.validator.db, "record_debug"
|
|
177
|
+
) as mock_record_debug:
|
|
178
|
+
self.validator.capture_lid()
|
|
179
|
+
mock_record_debug.assert_any_call("ACPI Lid (//lid0): open")
|
|
180
|
+
mock_record_debug.assert_any_call("ACPI Lid (//lid1): closed")
|
|
181
|
+
|
|
182
|
+
def test_capture_wakeup_irq_data(self):
|
|
183
|
+
"""Test capture_wakeup_irq_data method"""
|
|
184
|
+
with patch("os.path.join", side_effect=lambda *args: "/".join(args)), patch(
|
|
185
|
+
"amd_debug.validator.read_file",
|
|
186
|
+
side_effect=[
|
|
187
|
+
"123", # IRQ number
|
|
188
|
+
"chip_name_mock", # Chip name
|
|
189
|
+
"irq_name_mock", # IRQ name
|
|
190
|
+
"hw_mock", # Hardware IRQ
|
|
191
|
+
"actions_mock", # Actions
|
|
192
|
+
],
|
|
193
|
+
), patch.object(self.validator.db, "record_debug") as mock_record_debug:
|
|
194
|
+
result = self.validator.capture_wakeup_irq_data()
|
|
195
|
+
self.assertTrue(result)
|
|
196
|
+
mock_record_debug.assert_called_once_with(
|
|
197
|
+
"Woke up from IRQ 123 (chip_name_mock hw_mock-irq_name_mock actions_mock)"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def test_capture_thermal(self):
|
|
201
|
+
"""Test capture_thermal method"""
|
|
202
|
+
# Mock pyudev devices
|
|
203
|
+
mock_pyudev = patch.object(self.validator, "pyudev").start()
|
|
204
|
+
mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
205
|
+
mock_record_prereq = patch.object(self.validator.db, "record_prereq").start()
|
|
206
|
+
mock_read_file = patch("amd_debug.validator.read_file").start()
|
|
207
|
+
mock_os_listdir = patch("os.listdir").start()
|
|
208
|
+
|
|
209
|
+
# Mock thermal devices
|
|
210
|
+
mock_device = unittest.mock.Mock()
|
|
211
|
+
mock_device.device_path = "/devices/LNXTHERM:00"
|
|
212
|
+
mock_device.sys_path = "/sys/devices/LNXTHERM:00"
|
|
213
|
+
mock_pyudev.list_devices.return_value = [mock_device]
|
|
214
|
+
|
|
215
|
+
# Mock thermal zone files
|
|
216
|
+
mock_read_file.side_effect = [
|
|
217
|
+
"45000", # Current temperature in millidegrees
|
|
218
|
+
"critical", # Trip point 0 type
|
|
219
|
+
"50000", # Trip point 0 temperature in millidegrees
|
|
220
|
+
]
|
|
221
|
+
mock_os_listdir.return_value = ["trip_point_0_type", "trip_point_0_temp"]
|
|
222
|
+
|
|
223
|
+
# Call the method
|
|
224
|
+
result = self.validator.capture_thermal()
|
|
225
|
+
|
|
226
|
+
# Validate debug messages
|
|
227
|
+
mock_record_debug.assert_any_call("Thermal zones")
|
|
228
|
+
mock_record_debug.assert_any_call("└─LNXTHERM:00")
|
|
229
|
+
mock_record_debug.assert_any_call(" \t temp: 45.0°C")
|
|
230
|
+
mock_record_debug.assert_any_call(" \t critical trip: 50.0°C")
|
|
231
|
+
|
|
232
|
+
# Ensure no prereq was recorded since temp < trip
|
|
233
|
+
mock_record_prereq.assert_not_called()
|
|
234
|
+
|
|
235
|
+
# Stop patches
|
|
236
|
+
patch.stopall()
|
|
237
|
+
|
|
238
|
+
def test_capture_input_wakeup_count(self):
|
|
239
|
+
"""Test capture_input_wakeup_count method"""
|
|
240
|
+
# Mock pyudev devices
|
|
241
|
+
mock_pyudev = patch.object(self.validator, "pyudev").start()
|
|
242
|
+
mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
243
|
+
mock_read_file = patch("amd_debug.validator.read_file").start()
|
|
244
|
+
mock_os_path_exists = patch("os.path.exists").start()
|
|
245
|
+
|
|
246
|
+
# Mock input devices
|
|
247
|
+
mock_device = unittest.mock.Mock()
|
|
248
|
+
mock_device.sys_path = "/sys/devices/input0"
|
|
249
|
+
mock_device.parent = None
|
|
250
|
+
mock_pyudev.list_devices.return_value = [mock_device]
|
|
251
|
+
|
|
252
|
+
# Mock wakeup file existence and content
|
|
253
|
+
mock_os_path_exists.side_effect = (
|
|
254
|
+
lambda path: "wakeup" in path or "wakeup_count" in path
|
|
255
|
+
)
|
|
256
|
+
mock_read_file.side_effect = ["5"] # Wakeup count
|
|
257
|
+
|
|
258
|
+
# Set initial wakeup count
|
|
259
|
+
self.validator.wakeup_count = {"/sys/devices/input0": "3"}
|
|
260
|
+
|
|
261
|
+
# Call the method
|
|
262
|
+
self.validator.capture_input_wakeup_count()
|
|
263
|
+
|
|
264
|
+
# Validate debug messages
|
|
265
|
+
mock_record_debug.assert_called_once_with(
|
|
266
|
+
"Woke up from input source /sys/devices/input0 (3->5)", "💤"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Stop patches
|
|
270
|
+
patch.stopall()
|
|
271
|
+
|
|
272
|
+
def test_capture_hw_sleep_suspend_stats(self):
|
|
273
|
+
"""Test capture_hw_sleep stats method"""
|
|
274
|
+
# Case 1: Suspend stats file exists and contains valid data
|
|
275
|
+
with patch(
|
|
276
|
+
"os.path.exists", side_effect=lambda path: "suspend_stats" in path
|
|
277
|
+
), patch("amd_debug.validator.read_file", return_value="1000000"), patch.object(
|
|
278
|
+
self.validator.db, "record_cycle_data"
|
|
279
|
+
) as mock_record_cycle_data:
|
|
280
|
+
result = self.validator.capture_hw_sleep()
|
|
281
|
+
self.assertTrue(result)
|
|
282
|
+
self.assertEqual(self.validator.hw_sleep_duration, 1.0)
|
|
283
|
+
mock_record_cycle_data.assert_not_called()
|
|
284
|
+
|
|
285
|
+
def test_capture_hw_sleep_smu_fw_info(self):
|
|
286
|
+
"""Test capture_hw_sleep smu_fw_info method"""
|
|
287
|
+
# Case 2: Suspend stats file does not exist, fallback to smu_fw_info
|
|
288
|
+
with patch(
|
|
289
|
+
"os.path.exists", side_effect=lambda path: "suspend_stats" not in path
|
|
290
|
+
), patch(
|
|
291
|
+
"amd_debug.validator.read_file",
|
|
292
|
+
side_effect=[
|
|
293
|
+
"Last S0i3 Status: Success\nTime (in us) in S0i3: 2000000", # smu_fw_info content
|
|
294
|
+
],
|
|
295
|
+
), patch.object(
|
|
296
|
+
self.validator.db, "record_cycle_data"
|
|
297
|
+
) as mock_record_cycle_data:
|
|
298
|
+
result = self.validator.capture_hw_sleep()
|
|
299
|
+
self.assertTrue(result)
|
|
300
|
+
self.assertEqual(self.validator.hw_sleep_duration, 2.0)
|
|
301
|
+
mock_record_cycle_data.assert_not_called()
|
|
302
|
+
|
|
303
|
+
def test_capture_hw_sleep_smu_fw_info_lockdown(self):
|
|
304
|
+
"""Test capture_hw_sleep smu_fw_info method while locked down"""
|
|
305
|
+
# Case 3: PermissionError while reading smu_fw_info with lockdown enabled
|
|
306
|
+
self.validator.lockdown = True
|
|
307
|
+
with patch("os.path.exists", return_value=False), patch(
|
|
308
|
+
"amd_debug.validator.read_file", side_effect=PermissionError
|
|
309
|
+
), patch.object(
|
|
310
|
+
self.validator.db, "record_cycle_data"
|
|
311
|
+
) as mock_record_cycle_data:
|
|
312
|
+
result = self.validator.capture_hw_sleep()
|
|
313
|
+
self.assertFalse(result)
|
|
314
|
+
mock_record_cycle_data.assert_called_once_with(
|
|
315
|
+
"Unable to gather hardware sleep data with lockdown engaged", "🚦"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def test_capture_hw_sleep_smu_fw_info_missing(self):
|
|
319
|
+
"""Test capture_hw_sleep smu_fw_info missing method"""
|
|
320
|
+
# Case 4: FileNotFoundError while reading smu_fw_info
|
|
321
|
+
self.validator.lockdown = False
|
|
322
|
+
with patch("os.path.exists", return_value=False), patch(
|
|
323
|
+
"amd_debug.validator.read_file", side_effect=FileNotFoundError
|
|
324
|
+
), patch.object(self.validator.db, "record_debug") as mock_record:
|
|
325
|
+
result = self.validator.capture_hw_sleep()
|
|
326
|
+
self.assertFalse(result)
|
|
327
|
+
mock_record.assert_called_once_with(
|
|
328
|
+
"HW sleep statistics file /sys/kernel/debug/amd_pmc/smu_fw_info is missing"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def test_capture_amdgpu_ips_status(self):
|
|
332
|
+
"""Test capture_amdgpu_ips_status method"""
|
|
333
|
+
# Mock pyudev devices
|
|
334
|
+
mock_pyudev = patch.object(self.validator, "pyudev").start()
|
|
335
|
+
mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
336
|
+
mock_read_file = patch("amd_debug.validator.read_file").start()
|
|
337
|
+
mock_os_path_exists = patch("os.path.exists").start()
|
|
338
|
+
|
|
339
|
+
# Mock PCI devices
|
|
340
|
+
mock_device = unittest.mock.Mock()
|
|
341
|
+
mock_device.properties = {
|
|
342
|
+
"PCI_ID": "1002:abcd",
|
|
343
|
+
"PCI_SLOT_NAME": "0000:01:00.0",
|
|
344
|
+
}
|
|
345
|
+
mock_pyudev.list_devices.return_value = [mock_device]
|
|
346
|
+
|
|
347
|
+
# Case 1: IPS status file exists and is readable
|
|
348
|
+
mock_os_path_exists.return_value = True
|
|
349
|
+
mock_read_file.return_value = "IPS Enabled\nIPS Level: 2"
|
|
350
|
+
|
|
351
|
+
self.validator.capture_amdgpu_ips_status()
|
|
352
|
+
|
|
353
|
+
# Validate debug messages
|
|
354
|
+
mock_record_debug.assert_any_call("IPS status")
|
|
355
|
+
mock_record_debug.assert_any_call("│ IPS Enabled")
|
|
356
|
+
mock_record_debug.assert_any_call("└─IPS Level: 2")
|
|
357
|
+
|
|
358
|
+
# Case 2: IPS status file does not exist
|
|
359
|
+
mock_os_path_exists.return_value = False
|
|
360
|
+
self.validator.capture_amdgpu_ips_status()
|
|
361
|
+
|
|
362
|
+
# Case 3: PermissionError while reading IPS status file
|
|
363
|
+
mock_os_path_exists.return_value = True
|
|
364
|
+
mock_read_file.side_effect = PermissionError
|
|
365
|
+
self.validator.lockdown = True
|
|
366
|
+
self.validator.capture_amdgpu_ips_status()
|
|
367
|
+
mock_record_debug.assert_any_call(
|
|
368
|
+
"Unable to gather IPS state data due to kernel lockdown."
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# Case 4: PermissionError without lockdown
|
|
372
|
+
self.validator.lockdown = False
|
|
373
|
+
self.validator.capture_amdgpu_ips_status()
|
|
374
|
+
mock_record_debug.assert_any_call("Failed to read IPS state data")
|
|
375
|
+
|
|
376
|
+
# Stop patches
|
|
377
|
+
patch.stopall()
|
|
378
|
+
|
|
379
|
+
def test_analyze_kernel_log(self):
|
|
380
|
+
"""Test analyze_kernel_log method"""
|
|
381
|
+
# Mock kernel log lines
|
|
382
|
+
mock_kernel_log_lines = [
|
|
383
|
+
"Timekeeping suspended for 123456 us",
|
|
384
|
+
"Successfully transitioned to state lps0 ms entry",
|
|
385
|
+
"Triggering wakeup from IRQ 5",
|
|
386
|
+
"ACPI BIOS Error (bug): Something went wrong",
|
|
387
|
+
"Event logged [IO_PAGE_FAULT device=0000:00:0c.0 domain=0x0000 address=0x7e800000 flags=0x0050]",
|
|
388
|
+
"Dispatching Notify on [UBTC] (Device) Value 0x80 (Status Change)",
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
# Mock kernel log processing
|
|
392
|
+
mock_process_callback = patch.object(
|
|
393
|
+
self.validator.kernel_log, "process_callback"
|
|
394
|
+
).start()
|
|
395
|
+
mock_process_callback.side_effect = lambda callback: [
|
|
396
|
+
callback(line, 7) for line in mock_kernel_log_lines
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
# Mock database recording
|
|
400
|
+
mock_record_cycle_data = patch.object(
|
|
401
|
+
self.validator.db, "record_cycle_data"
|
|
402
|
+
).start()
|
|
403
|
+
mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
404
|
+
|
|
405
|
+
# Call the method
|
|
406
|
+
self.validator.analyze_kernel_log()
|
|
407
|
+
|
|
408
|
+
# Validate recorded cycle data
|
|
409
|
+
mock_record_cycle_data.assert_any_call("Hardware sleep cycle count: 1", "💤")
|
|
410
|
+
mock_record_cycle_data.assert_any_call("ACPI BIOS errors found", "❌")
|
|
411
|
+
mock_record_cycle_data.assert_any_call("Page faults found", "❌")
|
|
412
|
+
mock_record_cycle_data.assert_any_call(
|
|
413
|
+
"Notify devices ['UBTC'] found during suspend", "💤"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Validate recorded debug messages
|
|
417
|
+
mock_record_debug.assert_any_call("Used Microsoft uPEP GUID in LPS0 _DSM")
|
|
418
|
+
mock_record_debug.assert_any_call("Triggering wakeup from IRQ 5", 7)
|
|
419
|
+
|
|
420
|
+
# Stop patches
|
|
421
|
+
patch.stopall()
|
|
422
|
+
|
|
423
|
+
def test_prep(self):
|
|
424
|
+
"""Test prep method"""
|
|
425
|
+
with patch("amd_debug.validator.datetime") as mock_datetime, patch.object(
|
|
426
|
+
self.validator.kernel_log, "seek_tail"
|
|
427
|
+
) as mock_seek_tail, patch.object(
|
|
428
|
+
self.validator.db, "start_cycle"
|
|
429
|
+
) as mock_start_cycle, patch.object(
|
|
430
|
+
self.validator, "capture_battery"
|
|
431
|
+
) as mock_capture_battery, patch.object(
|
|
432
|
+
self.validator, "check_gpes"
|
|
433
|
+
) as mock_check_gpes, patch.object(
|
|
434
|
+
self.validator, "capture_lid"
|
|
435
|
+
) as mock_capture_lid, patch.object(
|
|
436
|
+
self.validator, "capture_command_line"
|
|
437
|
+
) as mock_capture_command_line, patch.object(
|
|
438
|
+
self.validator, "capture_wake_sources"
|
|
439
|
+
) as mock_capture_wake_sources, patch.object(
|
|
440
|
+
self.validator, "capture_running_compositors"
|
|
441
|
+
) as mock_capture_running_compositors, patch.object(
|
|
442
|
+
self.validator, "capture_power_profile"
|
|
443
|
+
) as mock_capture_power_profile, patch.object(
|
|
444
|
+
self.validator, "capture_amdgpu_ips_status"
|
|
445
|
+
) as mock_capture_amdgpu_ips_status, patch.object(
|
|
446
|
+
self.validator, "capture_thermal"
|
|
447
|
+
) as mock_capture_thermal, patch.object(
|
|
448
|
+
self.validator, "capture_input_wakeup_count"
|
|
449
|
+
) as mock_capture_input_wakeup_count, patch.object(
|
|
450
|
+
self.validator.acpica, "trace_bios"
|
|
451
|
+
) as mock_trace_bios, patch.object(
|
|
452
|
+
self.validator.acpica, "trace_notify"
|
|
453
|
+
) as mock_trace_notify, patch.object(
|
|
454
|
+
self.validator.db, "record_cycle"
|
|
455
|
+
) as mock_record_cycle:
|
|
456
|
+
|
|
457
|
+
# Mock datetime
|
|
458
|
+
mock_datetime.now.return_value = "mocked_datetime"
|
|
459
|
+
|
|
460
|
+
# Set bios_debug to True and test
|
|
461
|
+
self.validator.bios_debug = True
|
|
462
|
+
self.validator.prep()
|
|
463
|
+
mock_seek_tail.assert_called_once()
|
|
464
|
+
mock_start_cycle.assert_called_once_with("mocked_datetime")
|
|
465
|
+
mock_capture_battery.assert_called_once()
|
|
466
|
+
mock_check_gpes.assert_called_once()
|
|
467
|
+
mock_capture_lid.assert_called_once()
|
|
468
|
+
mock_capture_command_line.assert_called_once()
|
|
469
|
+
mock_capture_wake_sources.assert_called_once()
|
|
470
|
+
mock_capture_running_compositors.assert_called_once()
|
|
471
|
+
mock_capture_power_profile.assert_called_once()
|
|
472
|
+
mock_capture_amdgpu_ips_status.assert_called_once()
|
|
473
|
+
mock_capture_thermal.assert_called_once()
|
|
474
|
+
mock_capture_input_wakeup_count.assert_called_once()
|
|
475
|
+
mock_trace_bios.assert_called_once()
|
|
476
|
+
mock_trace_notify.assert_not_called()
|
|
477
|
+
mock_record_cycle.assert_called_once()
|
|
478
|
+
|
|
479
|
+
# Reset mocks
|
|
480
|
+
mock_seek_tail.reset_mock()
|
|
481
|
+
mock_start_cycle.reset_mock()
|
|
482
|
+
mock_capture_battery.reset_mock()
|
|
483
|
+
mock_check_gpes.reset_mock()
|
|
484
|
+
mock_capture_lid.reset_mock()
|
|
485
|
+
mock_capture_command_line.reset_mock()
|
|
486
|
+
mock_capture_wake_sources.reset_mock()
|
|
487
|
+
mock_capture_running_compositors.reset_mock()
|
|
488
|
+
mock_capture_power_profile.reset_mock()
|
|
489
|
+
mock_capture_amdgpu_ips_status.reset_mock()
|
|
490
|
+
mock_capture_thermal.reset_mock()
|
|
491
|
+
mock_capture_input_wakeup_count.reset_mock()
|
|
492
|
+
mock_trace_bios.reset_mock()
|
|
493
|
+
mock_trace_notify.reset_mock()
|
|
494
|
+
mock_record_cycle.reset_mock()
|
|
495
|
+
|
|
496
|
+
# Set bios_debug to False and test
|
|
497
|
+
self.validator.bios_debug = False
|
|
498
|
+
self.validator.prep()
|
|
499
|
+
mock_seek_tail.assert_called_once()
|
|
500
|
+
mock_start_cycle.assert_called_once_with("mocked_datetime")
|
|
501
|
+
mock_capture_battery.assert_called_once()
|
|
502
|
+
mock_check_gpes.assert_called_once()
|
|
503
|
+
mock_capture_lid.assert_called_once()
|
|
504
|
+
mock_capture_command_line.assert_called_once()
|
|
505
|
+
mock_capture_wake_sources.assert_called_once()
|
|
506
|
+
mock_capture_running_compositors.assert_called_once()
|
|
507
|
+
mock_capture_power_profile.assert_called_once()
|
|
508
|
+
mock_capture_amdgpu_ips_status.assert_called_once()
|
|
509
|
+
mock_capture_thermal.assert_called_once()
|
|
510
|
+
mock_capture_input_wakeup_count.assert_called_once()
|
|
511
|
+
mock_trace_bios.assert_not_called()
|
|
512
|
+
mock_trace_notify.assert_called_once()
|
|
513
|
+
mock_record_cycle.assert_called_once()
|
|
514
|
+
|
|
515
|
+
def test_post(self):
|
|
516
|
+
"""Test post method"""
|
|
517
|
+
with patch.object(
|
|
518
|
+
self.validator, "analyze_kernel_log"
|
|
519
|
+
) as mock_analyze_kernel_log, patch.object(
|
|
520
|
+
self.validator, "capture_wakeup_irq_data"
|
|
521
|
+
) as mock_capture_wakeup_irq_data, patch.object(
|
|
522
|
+
self.validator, "check_gpes"
|
|
523
|
+
) as mock_check_gpes, patch.object(
|
|
524
|
+
self.validator, "capture_lid"
|
|
525
|
+
) as mock_capture_lid, patch.object(
|
|
526
|
+
self.validator, "check_rtc_cmos"
|
|
527
|
+
) as mock_check_rtc_cmos, patch.object(
|
|
528
|
+
self.validator, "capture_hw_sleep"
|
|
529
|
+
) as mock_capture_hw_sleep, patch.object(
|
|
530
|
+
self.validator, "capture_battery"
|
|
531
|
+
) as mock_capture_battery, patch.object(
|
|
532
|
+
self.validator, "capture_amdgpu_ips_status"
|
|
533
|
+
) as mock_capture_amdgpu_ips_status, patch.object(
|
|
534
|
+
self.validator, "capture_thermal"
|
|
535
|
+
) as mock_capture_thermal, patch.object(
|
|
536
|
+
self.validator, "capture_input_wakeup_count"
|
|
537
|
+
) as mock_capture_input_wakeup_count, patch.object(
|
|
538
|
+
self.validator.acpica, "restore"
|
|
539
|
+
) as mock_acpica_restore, patch.object(
|
|
540
|
+
self.validator.db, "record_cycle"
|
|
541
|
+
) as mock_record_cycle:
|
|
542
|
+
|
|
543
|
+
# Set mock return values
|
|
544
|
+
mock_analyze_kernel_log.return_value = None
|
|
545
|
+
mock_capture_wakeup_irq_data.return_value = None
|
|
546
|
+
mock_check_gpes.return_value = None
|
|
547
|
+
mock_capture_lid.return_value = None
|
|
548
|
+
mock_check_rtc_cmos.return_value = None
|
|
549
|
+
mock_capture_hw_sleep.return_value = None
|
|
550
|
+
mock_capture_battery.return_value = None
|
|
551
|
+
mock_capture_amdgpu_ips_status.return_value = None
|
|
552
|
+
mock_capture_thermal.return_value = None
|
|
553
|
+
mock_capture_input_wakeup_count.return_value = None
|
|
554
|
+
mock_acpica_restore.return_value = None
|
|
555
|
+
|
|
556
|
+
# Set attributes for record_cycle
|
|
557
|
+
self.validator.requested_duration = 60
|
|
558
|
+
self.validator.active_gpios = ["GPIO1"]
|
|
559
|
+
self.validator.wakeup_irqs = [5]
|
|
560
|
+
self.validator.kernel_duration = 1.5
|
|
561
|
+
self.validator.hw_sleep_duration = 1.0
|
|
562
|
+
|
|
563
|
+
# Call the method
|
|
564
|
+
self.validator.post()
|
|
565
|
+
|
|
566
|
+
# Assert all checks were called
|
|
567
|
+
mock_analyze_kernel_log.assert_called_once()
|
|
568
|
+
mock_capture_wakeup_irq_data.assert_called_once()
|
|
569
|
+
mock_check_gpes.assert_called_once()
|
|
570
|
+
mock_capture_lid.assert_called_once()
|
|
571
|
+
mock_check_rtc_cmos.assert_called_once()
|
|
572
|
+
mock_capture_hw_sleep.assert_called_once()
|
|
573
|
+
mock_capture_battery.assert_called_once()
|
|
574
|
+
mock_capture_amdgpu_ips_status.assert_called_once()
|
|
575
|
+
mock_capture_thermal.assert_called_once()
|
|
576
|
+
mock_capture_input_wakeup_count.assert_called_once()
|
|
577
|
+
mock_acpica_restore.assert_called_once()
|
|
578
|
+
|
|
579
|
+
# Assert record_cycle was called with correct arguments
|
|
580
|
+
mock_record_cycle.assert_called_once_with(
|
|
581
|
+
self.validator.requested_duration,
|
|
582
|
+
self.validator.active_gpios,
|
|
583
|
+
self.validator.wakeup_irqs,
|
|
584
|
+
self.validator.kernel_duration,
|
|
585
|
+
self.validator.hw_sleep_duration,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
def test_program_wakealarm(self):
|
|
589
|
+
"""Test program_wakealarm method"""
|
|
590
|
+
# Mock pyudev devices
|
|
591
|
+
mock_pyudev = patch.object(self.validator, "pyudev").start()
|
|
592
|
+
_mock_record_debug = patch.object(self.validator.db, "record_debug").start()
|
|
593
|
+
mock_print_color = patch("amd_debug.validator.print_color").start()
|
|
594
|
+
mock_open_file = patch("builtins.open", mock_open()).start()
|
|
595
|
+
|
|
596
|
+
# Case 1: RTC device exists
|
|
597
|
+
mock_device = unittest.mock.Mock()
|
|
598
|
+
mock_device.sys_path = "/sys/class/rtc/rtc0"
|
|
599
|
+
mock_pyudev.list_devices.return_value = [mock_device]
|
|
600
|
+
self.validator.requested_duration = 60
|
|
601
|
+
|
|
602
|
+
self.validator.program_wakealarm()
|
|
603
|
+
|
|
604
|
+
# Validate file writes
|
|
605
|
+
mock_open_file.assert_any_call(
|
|
606
|
+
"/sys/class/rtc/rtc0/wakealarm", "w", encoding="utf-8"
|
|
607
|
+
)
|
|
608
|
+
mock_open_file().write.assert_any_call("0")
|
|
609
|
+
mock_open_file().write.assert_any_call("+60\n")
|
|
610
|
+
|
|
611
|
+
# Case 2: No RTC device found
|
|
612
|
+
mock_pyudev.list_devices.return_value = []
|
|
613
|
+
self.validator.program_wakealarm()
|
|
614
|
+
|
|
615
|
+
# Validate print_color call
|
|
616
|
+
mock_print_color.assert_called_once_with(
|
|
617
|
+
"No RTC device found, please manually wake system", "🚦"
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
# Stop patches
|
|
621
|
+
patch.stopall()
|
|
622
|
+
|
|
623
|
+
@patch("amd_debug.validator.SleepReport")
|
|
624
|
+
@patch("amd_debug.validator.print_color")
|
|
625
|
+
def test_report_cycle(self, mock_print_color, mock_sleep_report):
|
|
626
|
+
"""Test report_cycle method"""
|
|
627
|
+
# Mock SleepReport instance
|
|
628
|
+
mock_report_instance = mock_sleep_report.return_value
|
|
629
|
+
mock_report_instance.run.return_value = None
|
|
630
|
+
|
|
631
|
+
# Set attributes for the test
|
|
632
|
+
self.validator.last_suspend = "mocked_last_suspend"
|
|
633
|
+
self.validator.display_debug = True
|
|
634
|
+
|
|
635
|
+
# Call the method
|
|
636
|
+
self.validator.report_cycle()
|
|
637
|
+
|
|
638
|
+
# Assert print_color was called with correct arguments
|
|
639
|
+
mock_print_color.assert_called_once_with("Results from last s2idle cycle", "🗣️")
|
|
640
|
+
|
|
641
|
+
# Assert SleepReport was instantiated with correct arguments
|
|
642
|
+
mock_sleep_report.assert_called_once_with(
|
|
643
|
+
since="mocked_last_suspend",
|
|
644
|
+
until="mocked_last_suspend",
|
|
645
|
+
fname=None,
|
|
646
|
+
fmt="stdout",
|
|
647
|
+
tool_debug=True,
|
|
648
|
+
report_debug=False,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# Assert run method of SleepReport was called
|
|
652
|
+
mock_report_instance.run.assert_called_once_with(inc_prereq=False)
|
|
653
|
+
|
|
654
|
+
@patch("amd_debug.validator.run_countdown")
|
|
655
|
+
@patch("amd_debug.validator.random.randint")
|
|
656
|
+
@patch("amd_debug.validator.datetime")
|
|
657
|
+
@patch.object(SleepValidator, "prep")
|
|
658
|
+
@patch.object(SleepValidator, "program_wakealarm")
|
|
659
|
+
@patch.object(SleepValidator, "suspend_system")
|
|
660
|
+
@patch.object(SleepValidator, "post")
|
|
661
|
+
@patch.object(SleepValidator, "unlock_session")
|
|
662
|
+
@patch.object(SleepValidator, "report_cycle")
|
|
663
|
+
@patch("amd_debug.validator.print_color")
|
|
664
|
+
def test_run(
|
|
665
|
+
self,
|
|
666
|
+
_mock_print_color,
|
|
667
|
+
mock_report_cycle,
|
|
668
|
+
mock_unlock_session,
|
|
669
|
+
mock_post,
|
|
670
|
+
mock_suspend_system,
|
|
671
|
+
mock_program_wakealarm,
|
|
672
|
+
mock_prep,
|
|
673
|
+
mock_datetime,
|
|
674
|
+
mock_randint,
|
|
675
|
+
mock_run_countdown,
|
|
676
|
+
):
|
|
677
|
+
"""Test the run method"""
|
|
678
|
+
# Mock datetime
|
|
679
|
+
mock_datetime.now.return_value = datetime(2023, 1, 1, 12, 0, 0)
|
|
680
|
+
|
|
681
|
+
# Mock suspend_system to return True
|
|
682
|
+
mock_suspend_system.return_value = True
|
|
683
|
+
|
|
684
|
+
# Test case 1: count is 0
|
|
685
|
+
result = self.validator.run(
|
|
686
|
+
duration=10, count=0, wait=5, rand=False, logind=False
|
|
687
|
+
)
|
|
688
|
+
self.assertTrue(result)
|
|
689
|
+
|
|
690
|
+
# Test case 2: logind is True
|
|
691
|
+
self.validator.run(duration=10, count=1, wait=5, rand=False, logind=True)
|
|
692
|
+
self.assertTrue(self.validator.logind)
|
|
693
|
+
|
|
694
|
+
# Test case 3: Randomized test
|
|
695
|
+
mock_randint.side_effect = [7, 3] # Random duration and wait
|
|
696
|
+
self.validator.run(duration=10, count=1, wait=5, rand=True, logind=False)
|
|
697
|
+
mock_randint.assert_any_call(1, 10)
|
|
698
|
+
mock_randint.assert_any_call(1, 5)
|
|
699
|
+
mock_run_countdown.assert_any_call("Suspending system", math.ceil(3 / 2))
|
|
700
|
+
mock_run_countdown.assert_any_call("Collecting data", math.ceil(3 / 2))
|
|
701
|
+
mock_prep.assert_called()
|
|
702
|
+
mock_program_wakealarm.assert_called()
|
|
703
|
+
mock_suspend_system.assert_called()
|
|
704
|
+
mock_post.assert_called()
|
|
705
|
+
mock_report_cycle.assert_called()
|
|
706
|
+
mock_unlock_session.assert_called()
|
|
707
|
+
|
|
708
|
+
# Test case 4: Multiple cycles
|
|
709
|
+
self.validator.run(duration=10, count=2, wait=5, rand=False, logind=False)
|
|
710
|
+
self.assertEqual(mock_prep.call_count, 4) # Includes previous calls
|
|
711
|
+
self.assertEqual(mock_program_wakealarm.call_count, 4)
|
|
712
|
+
self.assertEqual(mock_suspend_system.call_count, 4)
|
|
713
|
+
self.assertEqual(mock_post.call_count, 4)
|
|
714
|
+
self.assertEqual(mock_report_cycle.call_count, 4)
|
|
715
|
+
self.assertEqual(mock_unlock_session.call_count, 3)
|
|
716
|
+
|
|
717
|
+
# Test case 5: suspend_system fails
|
|
718
|
+
mock_suspend_system.return_value = False
|
|
719
|
+
result = self.validator.run(
|
|
720
|
+
duration=10, count=1, wait=5, rand=False, logind=False
|
|
721
|
+
)
|
|
722
|
+
self.assertFalse(result)
|
|
723
|
+
mock_report_cycle.assert_called()
|