amd-debug-tools 0.2.0__py3-none-any.whl → 0.2.1__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.

Potentially problematic release.


This version of amd-debug-tools might be problematic. Click here for more details.

test_validator.py ADDED
@@ -0,0 +1,725 @@
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(
325
+ self.validator.db, "record_cycle_data"
326
+ ) as mock_record_cycle_data:
327
+ result = self.validator.capture_hw_sleep()
328
+ self.assertFalse(result)
329
+ mock_record_cycle_data.assert_called_once_with(
330
+ "HW sleep statistics file missing", "❌"
331
+ )
332
+
333
+ def test_capture_amdgpu_ips_status(self):
334
+ """Test capture_amdgpu_ips_status method"""
335
+ # Mock pyudev devices
336
+ mock_pyudev = patch.object(self.validator, "pyudev").start()
337
+ mock_record_debug = patch.object(self.validator.db, "record_debug").start()
338
+ mock_read_file = patch("amd_debug.validator.read_file").start()
339
+ mock_os_path_exists = patch("os.path.exists").start()
340
+
341
+ # Mock PCI devices
342
+ mock_device = unittest.mock.Mock()
343
+ mock_device.properties = {
344
+ "PCI_ID": "1002:abcd",
345
+ "PCI_SLOT_NAME": "0000:01:00.0",
346
+ }
347
+ mock_pyudev.list_devices.return_value = [mock_device]
348
+
349
+ # Case 1: IPS status file exists and is readable
350
+ mock_os_path_exists.return_value = True
351
+ mock_read_file.return_value = "IPS Enabled\nIPS Level: 2"
352
+
353
+ self.validator.capture_amdgpu_ips_status()
354
+
355
+ # Validate debug messages
356
+ mock_record_debug.assert_any_call("IPS status")
357
+ mock_record_debug.assert_any_call("│ IPS Enabled")
358
+ mock_record_debug.assert_any_call("└─IPS Level: 2")
359
+
360
+ # Case 2: IPS status file does not exist
361
+ mock_os_path_exists.return_value = False
362
+ self.validator.capture_amdgpu_ips_status()
363
+
364
+ # Case 3: PermissionError while reading IPS status file
365
+ mock_os_path_exists.return_value = True
366
+ mock_read_file.side_effect = PermissionError
367
+ self.validator.lockdown = True
368
+ self.validator.capture_amdgpu_ips_status()
369
+ mock_record_debug.assert_any_call(
370
+ "Unable to gather IPS state data due to kernel lockdown."
371
+ )
372
+
373
+ # Case 4: PermissionError without lockdown
374
+ self.validator.lockdown = False
375
+ self.validator.capture_amdgpu_ips_status()
376
+ mock_record_debug.assert_any_call("Failed to read IPS state data")
377
+
378
+ # Stop patches
379
+ patch.stopall()
380
+
381
+ def test_analyze_kernel_log(self):
382
+ """Test analyze_kernel_log method"""
383
+ # Mock kernel log lines
384
+ mock_kernel_log_lines = [
385
+ "Timekeeping suspended for 123456 us",
386
+ "Successfully transitioned to state lps0 ms entry",
387
+ "Triggering wakeup from IRQ 5",
388
+ "ACPI BIOS Error (bug): Something went wrong",
389
+ "Event logged [IO_PAGE_FAULT device=0000:00:0c.0 domain=0x0000 address=0x7e800000 flags=0x0050]",
390
+ "Dispatching Notify on [UBTC] (Device) Value 0x80 (Status Change)",
391
+ ]
392
+
393
+ # Mock kernel log processing
394
+ mock_process_callback = patch.object(
395
+ self.validator.kernel_log, "process_callback"
396
+ ).start()
397
+ mock_process_callback.side_effect = lambda callback: [
398
+ callback(line, 7) for line in mock_kernel_log_lines
399
+ ]
400
+
401
+ # Mock database recording
402
+ mock_record_cycle_data = patch.object(
403
+ self.validator.db, "record_cycle_data"
404
+ ).start()
405
+ mock_record_debug = patch.object(self.validator.db, "record_debug").start()
406
+
407
+ # Call the method
408
+ self.validator.analyze_kernel_log()
409
+
410
+ # Validate recorded cycle data
411
+ mock_record_cycle_data.assert_any_call("Hardware sleep cycle count: 1", "💤")
412
+ mock_record_cycle_data.assert_any_call("ACPI BIOS errors found", "❌")
413
+ mock_record_cycle_data.assert_any_call("Page faults found", "❌")
414
+ mock_record_cycle_data.assert_any_call(
415
+ "Notify devices ['UBTC'] found during suspend", "💤"
416
+ )
417
+
418
+ # Validate recorded debug messages
419
+ mock_record_debug.assert_any_call("Used Microsoft uPEP GUID in LPS0 _DSM")
420
+ mock_record_debug.assert_any_call("Triggering wakeup from IRQ 5", 7)
421
+
422
+ # Stop patches
423
+ patch.stopall()
424
+
425
+ def test_prep(self):
426
+ """Test prep method"""
427
+ with patch("amd_debug.validator.datetime") as mock_datetime, patch.object(
428
+ self.validator.kernel_log, "seek_tail"
429
+ ) as mock_seek_tail, patch.object(
430
+ self.validator.db, "start_cycle"
431
+ ) as mock_start_cycle, patch.object(
432
+ self.validator, "capture_battery"
433
+ ) as mock_capture_battery, patch.object(
434
+ self.validator, "check_gpes"
435
+ ) as mock_check_gpes, patch.object(
436
+ self.validator, "capture_lid"
437
+ ) as mock_capture_lid, patch.object(
438
+ self.validator, "capture_command_line"
439
+ ) as mock_capture_command_line, patch.object(
440
+ self.validator, "capture_wake_sources"
441
+ ) as mock_capture_wake_sources, patch.object(
442
+ self.validator, "capture_running_compositors"
443
+ ) as mock_capture_running_compositors, patch.object(
444
+ self.validator, "capture_power_profile"
445
+ ) as mock_capture_power_profile, patch.object(
446
+ self.validator, "capture_amdgpu_ips_status"
447
+ ) as mock_capture_amdgpu_ips_status, patch.object(
448
+ self.validator, "capture_thermal"
449
+ ) as mock_capture_thermal, patch.object(
450
+ self.validator, "capture_input_wakeup_count"
451
+ ) as mock_capture_input_wakeup_count, patch.object(
452
+ self.validator.acpica, "trace_bios"
453
+ ) as mock_trace_bios, patch.object(
454
+ self.validator.acpica, "trace_notify"
455
+ ) as mock_trace_notify, patch.object(
456
+ self.validator.db, "record_cycle"
457
+ ) as mock_record_cycle:
458
+
459
+ # Mock datetime
460
+ mock_datetime.now.return_value = "mocked_datetime"
461
+
462
+ # Set bios_debug to True and test
463
+ self.validator.bios_debug = True
464
+ self.validator.prep()
465
+ mock_seek_tail.assert_called_once()
466
+ mock_start_cycle.assert_called_once_with("mocked_datetime")
467
+ mock_capture_battery.assert_called_once()
468
+ mock_check_gpes.assert_called_once()
469
+ mock_capture_lid.assert_called_once()
470
+ mock_capture_command_line.assert_called_once()
471
+ mock_capture_wake_sources.assert_called_once()
472
+ mock_capture_running_compositors.assert_called_once()
473
+ mock_capture_power_profile.assert_called_once()
474
+ mock_capture_amdgpu_ips_status.assert_called_once()
475
+ mock_capture_thermal.assert_called_once()
476
+ mock_capture_input_wakeup_count.assert_called_once()
477
+ mock_trace_bios.assert_called_once()
478
+ mock_trace_notify.assert_not_called()
479
+ mock_record_cycle.assert_called_once()
480
+
481
+ # Reset mocks
482
+ mock_seek_tail.reset_mock()
483
+ mock_start_cycle.reset_mock()
484
+ mock_capture_battery.reset_mock()
485
+ mock_check_gpes.reset_mock()
486
+ mock_capture_lid.reset_mock()
487
+ mock_capture_command_line.reset_mock()
488
+ mock_capture_wake_sources.reset_mock()
489
+ mock_capture_running_compositors.reset_mock()
490
+ mock_capture_power_profile.reset_mock()
491
+ mock_capture_amdgpu_ips_status.reset_mock()
492
+ mock_capture_thermal.reset_mock()
493
+ mock_capture_input_wakeup_count.reset_mock()
494
+ mock_trace_bios.reset_mock()
495
+ mock_trace_notify.reset_mock()
496
+ mock_record_cycle.reset_mock()
497
+
498
+ # Set bios_debug to False and test
499
+ self.validator.bios_debug = False
500
+ self.validator.prep()
501
+ mock_seek_tail.assert_called_once()
502
+ mock_start_cycle.assert_called_once_with("mocked_datetime")
503
+ mock_capture_battery.assert_called_once()
504
+ mock_check_gpes.assert_called_once()
505
+ mock_capture_lid.assert_called_once()
506
+ mock_capture_command_line.assert_called_once()
507
+ mock_capture_wake_sources.assert_called_once()
508
+ mock_capture_running_compositors.assert_called_once()
509
+ mock_capture_power_profile.assert_called_once()
510
+ mock_capture_amdgpu_ips_status.assert_called_once()
511
+ mock_capture_thermal.assert_called_once()
512
+ mock_capture_input_wakeup_count.assert_called_once()
513
+ mock_trace_bios.assert_not_called()
514
+ mock_trace_notify.assert_called_once()
515
+ mock_record_cycle.assert_called_once()
516
+
517
+ def test_post(self):
518
+ """Test post method"""
519
+ with patch.object(
520
+ self.validator, "analyze_kernel_log"
521
+ ) as mock_analyze_kernel_log, patch.object(
522
+ self.validator, "capture_wakeup_irq_data"
523
+ ) as mock_capture_wakeup_irq_data, patch.object(
524
+ self.validator, "check_gpes"
525
+ ) as mock_check_gpes, patch.object(
526
+ self.validator, "capture_lid"
527
+ ) as mock_capture_lid, patch.object(
528
+ self.validator, "check_rtc_cmos"
529
+ ) as mock_check_rtc_cmos, patch.object(
530
+ self.validator, "capture_hw_sleep"
531
+ ) as mock_capture_hw_sleep, patch.object(
532
+ self.validator, "capture_battery"
533
+ ) as mock_capture_battery, patch.object(
534
+ self.validator, "capture_amdgpu_ips_status"
535
+ ) as mock_capture_amdgpu_ips_status, patch.object(
536
+ self.validator, "capture_thermal"
537
+ ) as mock_capture_thermal, patch.object(
538
+ self.validator, "capture_input_wakeup_count"
539
+ ) as mock_capture_input_wakeup_count, patch.object(
540
+ self.validator.acpica, "restore"
541
+ ) as mock_acpica_restore, patch.object(
542
+ self.validator.db, "record_cycle"
543
+ ) as mock_record_cycle:
544
+
545
+ # Set mock return values
546
+ mock_analyze_kernel_log.return_value = None
547
+ mock_capture_wakeup_irq_data.return_value = None
548
+ mock_check_gpes.return_value = None
549
+ mock_capture_lid.return_value = None
550
+ mock_check_rtc_cmos.return_value = None
551
+ mock_capture_hw_sleep.return_value = None
552
+ mock_capture_battery.return_value = None
553
+ mock_capture_amdgpu_ips_status.return_value = None
554
+ mock_capture_thermal.return_value = None
555
+ mock_capture_input_wakeup_count.return_value = None
556
+ mock_acpica_restore.return_value = None
557
+
558
+ # Set attributes for record_cycle
559
+ self.validator.requested_duration = 60
560
+ self.validator.active_gpios = ["GPIO1"]
561
+ self.validator.wakeup_irqs = [5]
562
+ self.validator.kernel_duration = 1.5
563
+ self.validator.hw_sleep_duration = 1.0
564
+
565
+ # Call the method
566
+ self.validator.post()
567
+
568
+ # Assert all checks were called
569
+ mock_analyze_kernel_log.assert_called_once()
570
+ mock_capture_wakeup_irq_data.assert_called_once()
571
+ mock_check_gpes.assert_called_once()
572
+ mock_capture_lid.assert_called_once()
573
+ mock_check_rtc_cmos.assert_called_once()
574
+ mock_capture_hw_sleep.assert_called_once()
575
+ mock_capture_battery.assert_called_once()
576
+ mock_capture_amdgpu_ips_status.assert_called_once()
577
+ mock_capture_thermal.assert_called_once()
578
+ mock_capture_input_wakeup_count.assert_called_once()
579
+ mock_acpica_restore.assert_called_once()
580
+
581
+ # Assert record_cycle was called with correct arguments
582
+ mock_record_cycle.assert_called_once_with(
583
+ self.validator.requested_duration,
584
+ self.validator.active_gpios,
585
+ self.validator.wakeup_irqs,
586
+ self.validator.kernel_duration,
587
+ self.validator.hw_sleep_duration,
588
+ )
589
+
590
+ def test_program_wakealarm(self):
591
+ """Test program_wakealarm method"""
592
+ # Mock pyudev devices
593
+ mock_pyudev = patch.object(self.validator, "pyudev").start()
594
+ _mock_record_debug = patch.object(self.validator.db, "record_debug").start()
595
+ mock_print_color = patch("amd_debug.validator.print_color").start()
596
+ mock_open_file = patch("builtins.open", mock_open()).start()
597
+
598
+ # Case 1: RTC device exists
599
+ mock_device = unittest.mock.Mock()
600
+ mock_device.sys_path = "/sys/class/rtc/rtc0"
601
+ mock_pyudev.list_devices.return_value = [mock_device]
602
+ self.validator.requested_duration = 60
603
+
604
+ self.validator.program_wakealarm()
605
+
606
+ # Validate file writes
607
+ mock_open_file.assert_any_call(
608
+ "/sys/class/rtc/rtc0/wakealarm", "w", encoding="utf-8"
609
+ )
610
+ mock_open_file().write.assert_any_call("0")
611
+ mock_open_file().write.assert_any_call("+60\n")
612
+
613
+ # Case 2: No RTC device found
614
+ mock_pyudev.list_devices.return_value = []
615
+ self.validator.program_wakealarm()
616
+
617
+ # Validate print_color call
618
+ mock_print_color.assert_called_once_with(
619
+ "No RTC device found, please manually wake system", "🚦"
620
+ )
621
+
622
+ # Stop patches
623
+ patch.stopall()
624
+
625
+ @patch("amd_debug.validator.SleepReport")
626
+ @patch("amd_debug.validator.print_color")
627
+ def test_report_cycle(self, mock_print_color, mock_sleep_report):
628
+ """Test report_cycle method"""
629
+ # Mock SleepReport instance
630
+ mock_report_instance = mock_sleep_report.return_value
631
+ mock_report_instance.run.return_value = None
632
+
633
+ # Set attributes for the test
634
+ self.validator.last_suspend = "mocked_last_suspend"
635
+ self.validator.display_debug = True
636
+
637
+ # Call the method
638
+ self.validator.report_cycle()
639
+
640
+ # Assert print_color was called with correct arguments
641
+ mock_print_color.assert_called_once_with("Results from last s2idle cycle", "🗣️")
642
+
643
+ # Assert SleepReport was instantiated with correct arguments
644
+ mock_sleep_report.assert_called_once_with(
645
+ since="mocked_last_suspend",
646
+ until="mocked_last_suspend",
647
+ fname=None,
648
+ fmt="stdout",
649
+ tool_debug=True,
650
+ report_debug=False,
651
+ )
652
+
653
+ # Assert run method of SleepReport was called
654
+ mock_report_instance.run.assert_called_once_with(inc_prereq=False)
655
+
656
+ @patch("amd_debug.validator.run_countdown")
657
+ @patch("amd_debug.validator.random.randint")
658
+ @patch("amd_debug.validator.datetime")
659
+ @patch.object(SleepValidator, "prep")
660
+ @patch.object(SleepValidator, "program_wakealarm")
661
+ @patch.object(SleepValidator, "suspend_system")
662
+ @patch.object(SleepValidator, "post")
663
+ @patch.object(SleepValidator, "unlock_session")
664
+ @patch.object(SleepValidator, "report_cycle")
665
+ @patch("amd_debug.validator.print_color")
666
+ def test_run(
667
+ self,
668
+ _mock_print_color,
669
+ mock_report_cycle,
670
+ mock_unlock_session,
671
+ mock_post,
672
+ mock_suspend_system,
673
+ mock_program_wakealarm,
674
+ mock_prep,
675
+ mock_datetime,
676
+ mock_randint,
677
+ mock_run_countdown,
678
+ ):
679
+ """Test the run method"""
680
+ # Mock datetime
681
+ mock_datetime.now.return_value = datetime(2023, 1, 1, 12, 0, 0)
682
+
683
+ # Mock suspend_system to return True
684
+ mock_suspend_system.return_value = True
685
+
686
+ # Test case 1: count is 0
687
+ result = self.validator.run(
688
+ duration=10, count=0, wait=5, rand=False, logind=False
689
+ )
690
+ self.assertTrue(result)
691
+
692
+ # Test case 2: logind is True
693
+ self.validator.run(duration=10, count=1, wait=5, rand=False, logind=True)
694
+ self.assertTrue(self.validator.logind)
695
+
696
+ # Test case 3: Randomized test
697
+ mock_randint.side_effect = [7, 3] # Random duration and wait
698
+ self.validator.run(duration=10, count=1, wait=5, rand=True, logind=False)
699
+ mock_randint.assert_any_call(1, 10)
700
+ mock_randint.assert_any_call(1, 5)
701
+ mock_run_countdown.assert_any_call("Suspending system", math.ceil(3 / 2))
702
+ mock_run_countdown.assert_any_call("Collecting data", math.ceil(3 / 2))
703
+ mock_prep.assert_called()
704
+ mock_program_wakealarm.assert_called()
705
+ mock_suspend_system.assert_called()
706
+ mock_post.assert_called()
707
+ mock_report_cycle.assert_called()
708
+ mock_unlock_session.assert_called()
709
+
710
+ # Test case 4: Multiple cycles
711
+ self.validator.run(duration=10, count=2, wait=5, rand=False, logind=False)
712
+ self.assertEqual(mock_prep.call_count, 4) # Includes previous calls
713
+ self.assertEqual(mock_program_wakealarm.call_count, 4)
714
+ self.assertEqual(mock_suspend_system.call_count, 4)
715
+ self.assertEqual(mock_post.call_count, 4)
716
+ self.assertEqual(mock_report_cycle.call_count, 4)
717
+ self.assertEqual(mock_unlock_session.call_count, 3)
718
+
719
+ # Test case 5: suspend_system fails
720
+ mock_suspend_system.return_value = False
721
+ result = self.validator.run(
722
+ duration=10, count=1, wait=5, rand=False, logind=False
723
+ )
724
+ self.assertFalse(result)
725
+ mock_report_cycle.assert_called()