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.
test_prerequisites.py ADDED
@@ -0,0 +1,1935 @@
1
+ #!/usr/bin/python3
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ This module contains unit tests for the prerequisite functions in the amd-debug-tools package.
6
+ """
7
+
8
+ import logging
9
+ import unittest
10
+ import subprocess
11
+ from unittest.mock import patch, MagicMock, mock_open
12
+
13
+ from amd_debug.prerequisites import PrerequisiteValidator
14
+ from amd_debug.failures import *
15
+ from amd_debug.common import apply_prefix_wrapper, BIT
16
+
17
+
18
+ class TestPrerequisiteValidator(unittest.TestCase):
19
+
20
+ @classmethod
21
+ def setUpClass(cls):
22
+ logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
23
+
24
+ @patch("amd_debug.prerequisites.is_root", return_value=True)
25
+ @patch("amd_debug.prerequisites.get_kernel_log")
26
+ @patch("amd_debug.prerequisites.get_distro", return_value="Ubuntu")
27
+ @patch("amd_debug.prerequisites.read_file", return_value="mocked_cmdline")
28
+ @patch("amd_debug.prerequisites.pyudev.Context")
29
+ @patch("amd_debug.prerequisites.SleepDatabase")
30
+ def setUp(
31
+ self,
32
+ MockSleepDatabase,
33
+ MockPyudev,
34
+ _mock_read_file,
35
+ _mock_get_distro,
36
+ mock_get_kernel_log,
37
+ _mock_is_root,
38
+ ):
39
+ self.mock_db = MockSleepDatabase.return_value
40
+ self.mock_pyudev = MockPyudev.return_value
41
+ self.mock_kernel_log = mock_get_kernel_log.return_value
42
+ self.validator = PrerequisiteValidator(tool_debug=True)
43
+
44
+ def test_check_amdgpu_no_driver(self):
45
+ """Test check_amdgpu with no driver present"""
46
+ self.mock_pyudev.list_devices.return_value = [
47
+ MagicMock(
48
+ properties={"PCI_CLASS": "30000", "PCI_ID": "1002abcd", "DRIVER": None}
49
+ )
50
+ ]
51
+ result = self.validator.check_amdgpu()
52
+ self.assertFalse(result)
53
+ self.assertTrue(
54
+ any(isinstance(f, MissingAmdgpu) for f in self.validator.failures)
55
+ )
56
+
57
+ def test_check_amdgpu_with_driver(self):
58
+ """Test check_amdgpu with driver present"""
59
+ self.mock_pyudev.list_devices.return_value = [
60
+ MagicMock(
61
+ properties={
62
+ "PCI_CLASS": "30000",
63
+ "PCI_ID": "1002abcd",
64
+ "DRIVER": "amdgpu",
65
+ }
66
+ )
67
+ ]
68
+ result = self.validator.check_amdgpu()
69
+ self.assertTrue(result)
70
+
71
+ def test_check_wcn6855_bug_no_bug(self):
72
+ """Test check_wcn6855_bug with no bug present"""
73
+ self.mock_kernel_log.match_pattern.side_effect = lambda pattern: None
74
+ result = self.validator.check_wcn6855_bug()
75
+ self.assertTrue(result)
76
+
77
+ def test_check_storage_no_nvme(self):
78
+ """Test check_storage with no NVMe devices"""
79
+ self.mock_pyudev.list_devices.return_value = []
80
+ result = self.validator.check_storage()
81
+ self.assertTrue(result)
82
+
83
+ @patch("amd_debug.prerequisites.minimum_kernel", return_value=True)
84
+ def test_check_amd_hsmp_new_kernel(self, _mock_minimum_kernel):
85
+ """Test check_amd_hsmp with CONFIG_AMD_HSMP=y and kernel version >= 6.10"""
86
+ result = self.validator.check_amd_hsmp()
87
+ self.assertTrue(result)
88
+
89
+ @patch("amd_debug.prerequisites.read_file", return_value="CONFIG_AMD_HSMP=y")
90
+ @patch("os.path.exists", return_value=True)
91
+ @patch("amd_debug.prerequisites.minimum_kernel", return_value=False)
92
+ def test_check_amd_hsmp_conflict(
93
+ self, _mock_min_kernel, _mock_exists, _mock_read_file
94
+ ):
95
+ """Test check_amd_hsmp with CONFIG_AMD_HSMP=y and kernel version < 6.10"""
96
+ result = self.validator.check_amd_hsmp()
97
+ self.assertFalse(result)
98
+ self.assertTrue(any(isinstance(f, AmdHsmpBug) for f in self.validator.failures))
99
+
100
+ def test_check_amd_pmc_no_driver(self):
101
+ """Test check_amd_pmc with no driver"""
102
+ self.mock_pyudev.list_devices.return_value = []
103
+ result = self.validator.check_amd_pmc()
104
+ self.assertFalse(result)
105
+ self.assertTrue(
106
+ any(isinstance(f, MissingAmdPmc) for f in self.validator.failures)
107
+ )
108
+
109
+ def test_check_sleep_mode_not_supported(self):
110
+ """Test check_sleep_mode with no sleep mode support"""
111
+ with patch("os.path.exists", return_value=False):
112
+ result = self.validator.check_sleep_mode()
113
+ self.assertFalse(result)
114
+
115
+ def test_check_sleep_mode_s2idle(self):
116
+ """Test check_sleep_mode with s2idle mode"""
117
+ with patch("os.path.exists", return_value=True), patch(
118
+ "amd_debug.prerequisites.read_file", return_value="[s2idle]"
119
+ ):
120
+ result = self.validator.check_sleep_mode()
121
+ self.assertTrue(result)
122
+
123
+ def test_check_port_pm_override_non_family_19(self):
124
+ """Test check_port_pm_override with non-family 0x19 CPU"""
125
+ self.validator.cpu_family = 0x18
126
+ result = self.validator.check_port_pm_override()
127
+ self.assertTrue(result)
128
+
129
+ def test_check_port_pm_override_non_matching_model(self):
130
+ """Test check_port_pm_override with non-matching CPU model"""
131
+ self.validator.cpu_family = 0x19
132
+ self.validator.cpu_model = 0x72
133
+ result = self.validator.check_port_pm_override()
134
+ self.assertTrue(result)
135
+
136
+ @patch("amd_debug.prerequisites.version.parse")
137
+ def test_check_port_pm_override_smu_version_too_high(self, mock_version_parse):
138
+ """Test check_port_pm_override with SMU version > 76.60.0"""
139
+ self.validator.cpu_family = 0x19
140
+ self.validator.cpu_model = 0x74
141
+ mock_version_parse.side_effect = lambda v: v if isinstance(v, str) else None
142
+ self.validator.smu_version = "76.61.0"
143
+ result = self.validator.check_port_pm_override()
144
+ self.assertTrue(result)
145
+
146
+ @patch("amd_debug.prerequisites.version.parse")
147
+ def test_check_port_pm_override_smu_version_too_low(self, mock_version_parse):
148
+ """Test check_port_pm_override with SMU version < 76.18.0"""
149
+ self.validator.cpu_family = 0x19
150
+ self.validator.cpu_model = 0x74
151
+ mock_version_parse.side_effect = lambda v: v if isinstance(v, str) else None
152
+ self.validator.smu_version = "76.17.0"
153
+ result = self.validator.check_port_pm_override()
154
+ self.assertTrue(result)
155
+
156
+ @patch("amd_debug.prerequisites.read_file", return_value="pcie_port_pm=off")
157
+ def test_check_port_pm_override_cmdline_override(self, mock_read_file):
158
+ """Test check_port_pm_override with pcie_port_pm=off in cmdline"""
159
+ self.validator.cpu_family = 0x19
160
+ self.validator.cpu_model = 0x74
161
+ self.validator.smu_version = "76.50.0"
162
+ result = self.validator.check_port_pm_override()
163
+ self.assertTrue(result)
164
+
165
+ @patch("amd_debug.prerequisites.read_file", return_value="mocked_cmdline")
166
+ def test_check_port_pm_override_no_override(self, mock_read_file):
167
+ """Test check_port_pm_override without pcie_port_pm=off in cmdline"""
168
+ self.validator.cpu_family = 0x19
169
+ self.validator.cpu_model = 0x74
170
+ self.validator.smu_version = "76.50.0"
171
+ result = self.validator.check_port_pm_override()
172
+ self.assertFalse(result)
173
+
174
+ def test_check_iommu_disabled(self):
175
+ """Test check_iommu when IOMMU is disabled"""
176
+ self.validator.cpu_family = 0x1A
177
+ self.validator.cpu_model = 0x20
178
+ self.mock_pyudev.list_devices.return_value = []
179
+ result = self.validator.check_iommu()
180
+ self.assertTrue(result)
181
+ self.mock_db.record_prereq.assert_called_with("IOMMU disabled", "✅")
182
+
183
+ @patch(
184
+ "amd_debug.prerequisites.open",
185
+ new_callable=unittest.mock.mock_open,
186
+ read_data=b"\x00" * 45,
187
+ )
188
+ def test_check_iommu_no_dma_protection(self, _mock_open):
189
+ """Test check_iommu when DMA protection is not enabled"""
190
+ self.validator.cpu_family = 0x1A
191
+ self.validator.cpu_model = 0x20
192
+ iommu_device = MagicMock(sys_path="/sys/devices/iommu")
193
+ self.mock_pyudev.list_devices.side_effect = [
194
+ [iommu_device],
195
+ [],
196
+ [],
197
+ ]
198
+ result = self.validator.check_iommu()
199
+ self.assertFalse(result)
200
+ self.assertTrue(
201
+ any(isinstance(f, DMArNotEnabled) for f in self.validator.failures)
202
+ )
203
+ self.mock_db.record_prereq.assert_called_with(
204
+ "IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
205
+ )
206
+
207
+ @patch(
208
+ "amd_debug.prerequisites.open",
209
+ new_callable=unittest.mock.mock_open,
210
+ read_data=b"\x00" * 36 + b"\xff" * 4,
211
+ )
212
+ def test_check_iommu_missing_acpi_device(self, _mock_open):
213
+ """Test check_iommu when MSFT0201 ACPI device is missing"""
214
+ self.validator.cpu_family = 0x1A
215
+ self.validator.cpu_model = 0x20
216
+ iommu_device = MagicMock(sys_path="/sys/devices/iommu")
217
+ self.mock_pyudev.list_devices.side_effect = [
218
+ [iommu_device],
219
+ [],
220
+ [],
221
+ ]
222
+ result = self.validator.check_iommu()
223
+ self.assertFalse(result)
224
+ self.assertTrue(
225
+ any(isinstance(f, MissingIommuACPI) for f in self.validator.failures)
226
+ )
227
+ self.mock_db.record_prereq.assert_called_with(
228
+ "IOMMU is misconfigured: missing MSFT0201 ACPI device", "❌"
229
+ )
230
+
231
+ @patch(
232
+ "amd_debug.prerequisites.open",
233
+ new_callable=unittest.mock.mock_open,
234
+ read_data=b"\x00" * 36 + b"\xff" * 4,
235
+ )
236
+ def test_check_iommu_missing_policy(self, _mock_open):
237
+ """Test check_iommu when policy is not bound to MSFT0201"""
238
+ self.validator.cpu_family = 0x1A
239
+ self.validator.cpu_model = 0x20
240
+ iommu_device = MagicMock(sys_path="/sys/devices/iommu")
241
+ acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
242
+ platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
243
+ self.mock_pyudev.list_devices.side_effect = [
244
+ [iommu_device],
245
+ [acpi_device],
246
+ [platform_device],
247
+ ]
248
+ result = self.validator.check_iommu()
249
+ self.assertFalse(result)
250
+ self.assertTrue(
251
+ any(isinstance(f, MissingIommuPolicy) for f in self.validator.failures)
252
+ )
253
+
254
+ @patch(
255
+ "amd_debug.prerequisites.open",
256
+ new_callable=unittest.mock.mock_open,
257
+ read_data=b"\x00" * 36 + b"\xff" * 4,
258
+ )
259
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
260
+ def test_check_iommu_properly_configured(self, _mock_open, _mock_exists):
261
+ """Test check_iommu when IOMMU is properly configured"""
262
+ self.validator.cpu_family = 0x1A
263
+ self.validator.cpu_model = 0x20
264
+ iommu_device = MagicMock(sys_path="/sys/devices/iommu")
265
+ acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
266
+ platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
267
+ self.mock_pyudev.list_devices.side_effect = [
268
+ [iommu_device],
269
+ [acpi_device],
270
+ [platform_device],
271
+ ]
272
+ result = self.validator.check_iommu()
273
+ self.assertTrue(result)
274
+ self.mock_db.record_prereq.assert_called_with("IOMMU properly configured", "✅")
275
+
276
+ @patch("amd_debug.prerequisites.read_file")
277
+ @patch("amd_debug.validator.print_color")
278
+ def test_check_taint_not_tainted(self, _mock_print_color, mock_read_file):
279
+ """Test check_taint when the kernel is not tainted"""
280
+ mock_read_file.return_value = "0"
281
+ result = self.validator.check_taint()
282
+ self.assertTrue(result)
283
+ self.assertFalse(
284
+ any(isinstance(f, TaintedKernel) for f in self.validator.failures)
285
+ )
286
+
287
+ @patch("amd_debug.prerequisites.read_file")
288
+ @patch("amd_debug.validator.print_color")
289
+ def test_check_taint_tainted(self, _mock_print_color, mock_read_file):
290
+ """Test check_taint when the kernel is tainted"""
291
+ mock_read_file.return_value = str(
292
+ BIT(9) | 1
293
+ ) # Kernel warnings ignored, other taint present
294
+ result = self.validator.check_taint()
295
+ self.assertFalse(result)
296
+ self.assertTrue(
297
+ any(isinstance(f, TaintedKernel) for f in self.validator.failures)
298
+ )
299
+
300
+ @patch("amd_debug.prerequisites.read_file")
301
+ @patch("amd_debug.validator.print_color")
302
+ def test_check_taint_file_not_found(self, _mock_print_color, mock_read_file):
303
+ """Test check_taint when the tainted file is not found"""
304
+ mock_read_file.side_effect = FileNotFoundError
305
+ with self.assertRaises(FileNotFoundError):
306
+ self.validator.check_taint()
307
+
308
+ @patch("amd_debug.prerequisites.read_file")
309
+ @patch("amd_debug.validator.print_color")
310
+ def test_check_taint_invalid_value(self, _mock_print_color, mock_read_file):
311
+ """Test check_taint when the tainted file contains invalid data"""
312
+ mock_read_file.return_value = "invalid"
313
+ with self.assertRaises(ValueError):
314
+ self.validator.check_taint()
315
+
316
+ @patch("amd_debug.prerequisites.read_file")
317
+ def test_check_smt_not_supported(self, mock_read_file):
318
+ """Test check_smt when SMT is not supported"""
319
+ mock_read_file.side_effect = ["notsupported"]
320
+ result = self.validator.check_smt()
321
+ self.assertTrue(result)
322
+ self.mock_db.record_debug.assert_called_with("SMT control: notsupported")
323
+
324
+ @patch("amd_debug.prerequisites.read_file")
325
+ def test_check_smt_disabled(self, mock_read_file):
326
+ """Test check_smt when SMT is disabled"""
327
+ mock_read_file.side_effect = ["on", "0"]
328
+ result = self.validator.check_smt()
329
+ self.assertFalse(result)
330
+ self.assertTrue(
331
+ any(isinstance(f, SMTNotEnabled) for f in self.validator.failures)
332
+ )
333
+ self.mock_db.record_prereq.assert_called_with("SMT is not enabled", "❌")
334
+
335
+ @patch("amd_debug.prerequisites.read_file")
336
+ def test_check_smt_enabled(self, mock_read_file):
337
+ """Test check_smt when SMT is enabled"""
338
+ mock_read_file.side_effect = ["on", "1"]
339
+ result = self.validator.check_smt()
340
+ self.assertTrue(result)
341
+ self.mock_db.record_prereq.assert_called_with("SMT enabled", "✅")
342
+
343
+ @patch("amd_debug.prerequisites.read_msr")
344
+ def test_check_msr_pc6_disabled(self, mock_read_msr):
345
+ """Test check_msr when PC6 is disabled"""
346
+ mock_read_msr.side_effect = lambda reg, _: (
347
+ 0 if reg == 0xC0010292 else BIT(22) | BIT(14) | BIT(6)
348
+ )
349
+ result = self.validator.check_msr()
350
+ self.assertFalse(result)
351
+ self.assertTrue(any(isinstance(f, MSRFailure) for f in self.validator.failures))
352
+
353
+ @patch("amd_debug.prerequisites.read_msr")
354
+ def test_check_msr_cc6_disabled(self, mock_read_msr):
355
+ """Test check_msr when CC6 is disabled"""
356
+ mock_read_msr.side_effect = lambda reg, _: BIT(32) if reg == 0xC0010292 else 0
357
+ result = self.validator.check_msr()
358
+ self.assertFalse(result)
359
+ self.assertTrue(any(isinstance(f, MSRFailure) for f in self.validator.failures))
360
+
361
+ @patch("amd_debug.prerequisites.read_msr")
362
+ def test_check_msr_enabled(self, mock_read_msr):
363
+ """Test check_msr when PC6 and CC6 are enabled"""
364
+ mock_read_msr.side_effect = lambda reg, _: (
365
+ BIT(32) if reg == 0xC0010292 else (BIT(22) | BIT(14) | BIT(6))
366
+ )
367
+ result = self.validator.check_msr()
368
+ self.assertTrue(result)
369
+ self.mock_db.record_prereq.assert_called_with("PC6 and CC6 enabled", "✅")
370
+
371
+ @patch("amd_debug.prerequisites.read_msr")
372
+ def test_check_msr_file_not_found(self, mock_read_msr):
373
+ """Test check_msr when MSR file is not found"""
374
+ mock_read_msr.side_effect = FileNotFoundError
375
+ result = self.validator.check_msr()
376
+ self.assertFalse(result)
377
+ self.mock_db.record_prereq.assert_called_with(
378
+ "Unable to check MSRs: MSR kernel module not loaded", "❌"
379
+ )
380
+
381
+ @patch("amd_debug.prerequisites.read_msr")
382
+ def test_check_msr_permission_error(self, mock_read_msr):
383
+ """Test check_msr when there is a permission error"""
384
+ mock_read_msr.side_effect = PermissionError
385
+ result = self.validator.check_msr()
386
+ self.assertTrue(result)
387
+ self.mock_db.record_prereq.assert_called_with("MSR checks unavailable", "🚦")
388
+
389
+ @patch("amd_debug.prerequisites.read_file")
390
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
391
+ def test_check_cpu_unsupported_model(self, mock_path_exists, mock_read_file):
392
+ """Test check_cpu with an unsupported CPU model"""
393
+ self.validator.cpu_family = 0x19
394
+ self.validator.cpu_model = 0x08
395
+ mock_read_file.return_value = "7"
396
+ result = self.validator.check_cpu()
397
+ self.assertFalse(result)
398
+ self.assertTrue(
399
+ any(isinstance(f, UnsupportedModel) for f in self.validator.failures)
400
+ )
401
+ self.mock_db.record_prereq.assert_called_with(
402
+ "This CPU model does not support hardware sleep over s2idle", "❌"
403
+ )
404
+
405
+ @patch("amd_debug.prerequisites.os.walk")
406
+ @patch(
407
+ "amd_debug.prerequisites.open",
408
+ new_callable=unittest.mock.mock_open,
409
+ read_data=b"mocked_data",
410
+ )
411
+ @patch("amd_debug.prerequisites.tempfile.mkdtemp", return_value="/mocked/tempdir")
412
+ @patch("amd_debug.prerequisites.subprocess.check_call")
413
+ @patch("amd_debug.prerequisites.shutil.rmtree")
414
+ def test_capture_acpi_success(
415
+ self, mock_rmtree, mock_check_call, mock_mkdtemp, mock_open, mock_walk
416
+ ):
417
+ """Test capture_acpi when ACPI tables are successfully captured"""
418
+ mock_walk.return_value = [
419
+ ("/sys/firmware/acpi/tables", [], ["SSDT1", "IVRS", "OTHER"]),
420
+ ]
421
+ result = self.validator.capture_acpi()
422
+ self.assertTrue(result)
423
+ mock_check_call.assert_called_with(
424
+ [
425
+ "iasl",
426
+ "-p",
427
+ "/mocked/tempdir/acpi",
428
+ "-d",
429
+ "/sys/firmware/acpi/tables/IVRS",
430
+ ],
431
+ stdout=subprocess.DEVNULL,
432
+ stderr=subprocess.DEVNULL,
433
+ )
434
+ self.mock_db.record_debug_file.assert_called_with("/mocked/tempdir/acpi.dsl")
435
+ mock_rmtree.assert_called_with("/mocked/tempdir")
436
+
437
+ @patch("amd_debug.prerequisites.os.walk")
438
+ @patch(
439
+ "amd_debug.prerequisites.open",
440
+ new_callable=unittest.mock.mock_open,
441
+ read_data=b"mocked_data",
442
+ )
443
+ @patch("amd_debug.prerequisites.tempfile.mkdtemp", return_value="/mocked/tempdir")
444
+ @patch(
445
+ "amd_debug.prerequisites.subprocess.check_call",
446
+ side_effect=subprocess.CalledProcessError(1, "iasl"),
447
+ )
448
+ @patch("amd_debug.prerequisites.shutil.rmtree")
449
+ def test_capture_acpi_subprocess_error(
450
+ self, mock_rmtree, mock_check_call, mock_mkdtemp, mock_open, mock_walk
451
+ ):
452
+ """Test capture_acpi when subprocess.check_call raises an error"""
453
+ mock_walk.return_value = [
454
+ ("/sys/firmware/acpi/tables", [], ["SSDT1", "IVRS", "OTHER"]),
455
+ ]
456
+ result = self.validator.capture_acpi()
457
+ self.assertTrue(result)
458
+ self.mock_db.record_prereq.assert_called_with(
459
+ "Failed to capture ACPI table: None", "👀"
460
+ )
461
+ mock_rmtree.assert_called_with("/mocked/tempdir")
462
+
463
+ @patch("amd_debug.prerequisites.os.walk")
464
+ @patch(
465
+ "amd_debug.prerequisites.open",
466
+ new_callable=unittest.mock.mock_open,
467
+ read_data=b"mocked_data",
468
+ )
469
+ @patch("amd_debug.prerequisites.tempfile.mkdtemp", return_value="/mocked/tempdir")
470
+ @patch("amd_debug.prerequisites.subprocess.check_call")
471
+ @patch("amd_debug.prerequisites.shutil.rmtree")
472
+ def test_capture_acpi_no_matching_files(
473
+ self, mock_rmtree, mock_check_call, mock_mkdtemp, mock_open, mock_walk
474
+ ):
475
+ """Test capture_acpi when no matching ACPI tables are found"""
476
+ mock_walk.return_value = [
477
+ ("/sys/firmware/acpi/tables", [], ["OTHER"]),
478
+ ]
479
+ result = self.validator.capture_acpi()
480
+ self.assertTrue(result)
481
+ mock_check_call.assert_not_called()
482
+ self.mock_db.record_debug_file.assert_not_called()
483
+ mock_rmtree.assert_not_called()
484
+
485
+ @patch("amd_debug.prerequisites.os.path.exists")
486
+ @patch("amd_debug.prerequisites.read_file")
487
+ @patch("amd_debug.prerequisites.os.readlink")
488
+ def test_map_acpi_path_with_devices(
489
+ self, mock_readlink, mock_read_file, mock_path_exists
490
+ ):
491
+ """Test map_acpi_path with valid ACPI devices"""
492
+ mock_path_exists.side_effect = lambda p: "path" in p or "driver" in p
493
+ mock_read_file.side_effect = lambda p: "mocked_path" if "path" in p else "1"
494
+ mock_readlink.return_value = "/mocked/driver"
495
+ self.mock_pyudev.list_devices.return_value = [
496
+ MagicMock(sys_path="/sys/devices/acpi/device1", sys_name="device1"),
497
+ MagicMock(sys_path="/sys/devices/acpi/device2", sys_name="device2"),
498
+ ]
499
+
500
+ result = self.validator.map_acpi_path()
501
+ self.assertTrue(result)
502
+ self.mock_db.record_debug.assert_called_with(
503
+ "ACPI name: ACPI path [driver]\n"
504
+ "│ device1: mocked_path [driver]\n"
505
+ "└─device2: mocked_path [driver]\n"
506
+ )
507
+
508
+ @patch("amd_debug.prerequisites.os.path.exists")
509
+ @patch("amd_debug.prerequisites.read_file")
510
+ def test_map_acpi_path_no_devices(self, mock_read_file, mock_path_exists):
511
+ """Test map_acpi_path with no valid ACPI devices"""
512
+ mock_path_exists.return_value = False
513
+ self.mock_pyudev.list_devices.return_value = []
514
+
515
+ result = self.validator.map_acpi_path()
516
+ self.assertTrue(result)
517
+
518
+ @patch("amd_debug.prerequisites.os.path.exists")
519
+ @patch("amd_debug.prerequisites.read_file")
520
+ def test_map_acpi_path_device_with_status_zero(
521
+ self, mock_read_file, mock_path_exists
522
+ ):
523
+ """Test map_acpi_path when a device has status 0"""
524
+ mock_path_exists.side_effect = lambda p: "path" in p or "status" in p
525
+ mock_read_file.side_effect = lambda p: "mocked_path" if "path" in p else "0"
526
+ self.mock_pyudev.list_devices.return_value = [
527
+ MagicMock(sys_path="/sys/devices/acpi/device1", sys_name="device1")
528
+ ]
529
+
530
+ result = self.validator.map_acpi_path()
531
+ self.assertTrue(result)
532
+
533
+ @patch("amd_debug.prerequisites.os.path.exists")
534
+ @patch("amd_debug.prerequisites.read_file")
535
+ def test_map_acpi_path_device_without_driver(
536
+ self, mock_read_file, mock_path_exists
537
+ ):
538
+ """Test map_acpi_path when a device does not have a driver"""
539
+ mock_path_exists.side_effect = lambda p: "path" in p
540
+ mock_read_file.side_effect = lambda p: "mocked_path"
541
+ self.mock_pyudev.list_devices.return_value = [
542
+ MagicMock(sys_path="/sys/devices/acpi/device1", sys_name="device1")
543
+ ]
544
+
545
+ result = self.validator.map_acpi_path()
546
+ self.assertTrue(result)
547
+ self.mock_db.record_debug.assert_called_with(
548
+ "ACPI name: ACPI path [driver]\n└─device1: mocked_path [None]\n"
549
+ )
550
+
551
+ @patch("amd_debug.prerequisites.read_file")
552
+ @patch("amd_debug.prerequisites.os.path.exists")
553
+ def test_capture_pci_acpi_with_acpi_path(self, mock_path_exists, mock_read_file):
554
+ """Test capture_pci_acpi when ACPI paths exist for devices"""
555
+ mock_path_exists.side_effect = lambda p: "firmware_node/path" in p
556
+ mock_read_file.side_effect = lambda p: "mocked_acpi_path"
557
+ self.mock_pyudev.list_devices.return_value = [
558
+ MagicMock(
559
+ properties={
560
+ "PCI_ID": "1234abcd",
561
+ "PCI_SLOT_NAME": "0000:00:1f.0",
562
+ "ID_PCI_SUBCLASS_FROM_DATABASE": "ISA bridge",
563
+ "ID_VENDOR_FROM_DATABASE": "Intel Corporation",
564
+ },
565
+ parent=MagicMock(subsystem="platform"),
566
+ sys_path="/sys/devices/pci0000:00/0000:00:1f.0",
567
+ )
568
+ ]
569
+
570
+ self.validator.capture_pci_acpi()
571
+ self.mock_db.record_debug.assert_called_with(
572
+ "PCI devices\n"
573
+ "└─0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
574
+ )
575
+
576
+ @patch("amd_debug.prerequisites.read_file")
577
+ @patch("amd_debug.prerequisites.os.path.exists")
578
+ def test_capture_pci_acpi_without_acpi_path(self, mock_path_exists, mock_read_file):
579
+ """Test capture_pci_acpi when ACPI paths do not exist for devices"""
580
+ mock_path_exists.return_value = False
581
+ self.mock_pyudev.list_devices.return_value = [
582
+ MagicMock(
583
+ properties={
584
+ "PCI_ID": "5678efgh",
585
+ "PCI_SLOT_NAME": "0000:01:00.0",
586
+ "ID_PCI_SUBCLASS_FROM_DATABASE": "VGA compatible controller",
587
+ "ID_VENDOR_FROM_DATABASE": "NVIDIA Corporation",
588
+ },
589
+ parent=MagicMock(subsystem="pci"),
590
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
591
+ )
592
+ ]
593
+
594
+ self.validator.capture_pci_acpi()
595
+ self.mock_db.record_debug.assert_called_with(
596
+ "PCI devices\n"
597
+ "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh]\n"
598
+ )
599
+
600
+ @patch("amd_debug.prerequisites.read_file")
601
+ @patch("amd_debug.prerequisites.os.path.exists")
602
+ def test_capture_pci_acpi_multiple_devices(self, mock_path_exists, mock_read_file):
603
+ """Test capture_pci_acpi with multiple devices"""
604
+ mock_path_exists.side_effect = lambda p: "firmware_node/path" in p
605
+ mock_read_file.side_effect = lambda p: "mocked_acpi_path" if "path" in p else ""
606
+ self.mock_pyudev.list_devices.return_value = [
607
+ MagicMock(
608
+ properties={
609
+ "PCI_ID": "1234abcd",
610
+ "PCI_SLOT_NAME": "0000:00:1f.0",
611
+ "ID_PCI_SUBCLASS_FROM_DATABASE": "ISA bridge",
612
+ "ID_VENDOR_FROM_DATABASE": "Intel Corporation",
613
+ },
614
+ parent=MagicMock(subsystem="platform"),
615
+ sys_path="/sys/devices/pci0000:00/0000:00:1f.0",
616
+ ),
617
+ MagicMock(
618
+ properties={
619
+ "PCI_ID": "5678efgh",
620
+ "PCI_SLOT_NAME": "0000:01:00.0",
621
+ "ID_PCI_SUBCLASS_FROM_DATABASE": "VGA compatible controller",
622
+ "ID_VENDOR_FROM_DATABASE": "NVIDIA Corporation",
623
+ },
624
+ parent=MagicMock(subsystem="pci"),
625
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
626
+ ),
627
+ ]
628
+
629
+ self.validator.capture_pci_acpi()
630
+ self.mock_db.record_debug.assert_called_with(
631
+ "PCI devices\n"
632
+ "│ 0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
633
+ "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh] : mocked_acpi_path\n"
634
+ )
635
+
636
+ def test_capture_pci_acpi_no_devices(self):
637
+ """Test capture_pci_acpi when no PCI devices are found"""
638
+ self.mock_pyudev.list_devices.return_value = []
639
+
640
+ self.validator.capture_pci_acpi()
641
+ self.mock_db.record_debug.assert_called_with("PCI devices\n")
642
+
643
+ @patch("amd_debug.prerequisites.read_file")
644
+ def test_check_aspm_default_policy(self, mock_read_file):
645
+ """Test check_aspm when the policy is set to default"""
646
+ mock_read_file.return_value = "[default]"
647
+ result = self.validator.check_aspm()
648
+ self.assertTrue(result)
649
+ self.mock_db.record_prereq.assert_called_with(
650
+ "ASPM policy set to 'default'", "✅"
651
+ )
652
+
653
+ @patch("amd_debug.prerequisites.read_file")
654
+ def test_check_aspm_non_default_policy(self, mock_read_file):
655
+ """Test check_aspm when the policy is not set to default"""
656
+ mock_read_file.return_value = "[performance]"
657
+ result = self.validator.check_aspm()
658
+ self.assertFalse(result)
659
+ self.mock_db.record_prereq.assert_called_with(
660
+ "ASPM policy set to [performance]", "❌"
661
+ )
662
+ self.assertTrue(any(isinstance(f, ASpmWrong) for f in self.validator.failures))
663
+
664
+ @patch("amd_debug.prerequisites.read_file")
665
+ def test_check_aspm_empty_policy(self, mock_read_file):
666
+ """Test check_aspm when the policy file is empty"""
667
+ mock_read_file.return_value = ""
668
+ result = self.validator.check_aspm()
669
+ self.assertFalse(result)
670
+ self.mock_db.record_prereq.assert_called_with("ASPM policy set to ", "❌")
671
+ self.assertTrue(any(isinstance(f, ASpmWrong) for f in self.validator.failures))
672
+
673
+ @patch("amd_debug.prerequisites.read_file")
674
+ def test_check_aspm_file_not_found(self, mock_read_file):
675
+ """Test check_aspm when the policy file is not found"""
676
+ mock_read_file.side_effect = FileNotFoundError
677
+ with self.assertRaises(FileNotFoundError):
678
+ self.validator.check_aspm()
679
+
680
+ @patch("amd_debug.prerequisites.os.path.exists")
681
+ @patch("amd_debug.prerequisites.read_file")
682
+ def test_check_i2c_hid_no_devices(self, mock_read_file, mock_path_exists):
683
+ """Test check_i2c_hid when no I2C HID devices are found"""
684
+ self.mock_pyudev.list_devices.return_value = []
685
+ result = self.validator.check_i2c_hid()
686
+ self.assertTrue(result)
687
+
688
+ @patch("amd_debug.prerequisites.os.path.exists")
689
+ @patch("amd_debug.prerequisites.read_file")
690
+ def test_check_i2c_hid_with_devices(self, mock_read_file, mock_path_exists):
691
+ """Test check_i2c_hid when I2C HID devices are found"""
692
+ mock_path_exists.side_effect = (
693
+ lambda p: "firmware_node/path" in p or "firmware_node/hid" in p
694
+ )
695
+ mock_read_file.side_effect = lambda p: (
696
+ "mocked_path" if "path" in p else "mocked_hid"
697
+ )
698
+ self.mock_pyudev.list_devices.return_value = [
699
+ MagicMock(
700
+ properties={"NAME": "I2C Device 1"},
701
+ find_parent=MagicMock(
702
+ return_value=MagicMock(sys_path="/sys/devices/i2c-1")
703
+ ),
704
+ ),
705
+ MagicMock(
706
+ properties={"NAME": "I2C Device 2"},
707
+ find_parent=MagicMock(
708
+ return_value=MagicMock(sys_path="/sys/devices/i2c-2")
709
+ ),
710
+ ),
711
+ ]
712
+
713
+ result = self.validator.check_i2c_hid()
714
+ self.assertTrue(result)
715
+ self.mock_db.record_debug.assert_called_with(
716
+ "I2C HID devices:\n"
717
+ "│ I2C Device 1 [mocked_hid] : mocked_path\n"
718
+ "└─I2C Device 2 [mocked_hid] : mocked_path\n"
719
+ )
720
+
721
+ @patch("amd_debug.prerequisites.os.path.exists")
722
+ @patch("amd_debug.prerequisites.read_file")
723
+ def test_check_i2c_hid_with_buggy_device(self, mock_read_file, mock_path_exists):
724
+ """Test check_i2c_hid when a buggy I2C HID device is found"""
725
+ mock_path_exists.side_effect = (
726
+ lambda p: "firmware_node/path" in p or "firmware_node/hid" in p
727
+ )
728
+ mock_read_file.side_effect = lambda p: (
729
+ "mocked_path" if "path" in p else "mocked_hid"
730
+ )
731
+ self.mock_pyudev.list_devices.return_value = [
732
+ MagicMock(
733
+ properties={"NAME": "IDEA5002"},
734
+ find_parent=MagicMock(
735
+ return_value=MagicMock(
736
+ sys_path="/sys/devices/i2c-1", driver="mock_driver"
737
+ )
738
+ ),
739
+ )
740
+ ]
741
+
742
+ result = self.validator.check_i2c_hid()
743
+ self.assertFalse(result)
744
+ self.mock_db.record_prereq.assert_called_with(
745
+ "IDEA5002 may cause spurious wakeups", "❌"
746
+ )
747
+ self.assertTrue(any(isinstance(f, I2CHidBug) for f in self.validator.failures))
748
+
749
+ @patch("amd_debug.prerequisites.os.path.exists")
750
+ @patch("amd_debug.prerequisites.read_file")
751
+ def test_check_i2c_hid_missing_firmware_node(
752
+ self, mock_read_file, mock_path_exists
753
+ ):
754
+ """Test check_i2c_hid when firmware_node paths are missing"""
755
+ mock_path_exists.return_value = False
756
+ self.mock_pyudev.list_devices.return_value = [
757
+ MagicMock(
758
+ properties={"NAME": "I2C Device 1"},
759
+ find_parent=MagicMock(
760
+ return_value=MagicMock(sys_path="/sys/devices/i2c-1")
761
+ ),
762
+ )
763
+ ]
764
+
765
+ result = self.validator.check_i2c_hid()
766
+ self.assertTrue(result)
767
+ self.mock_db.record_debug.assert_called_with(
768
+ "I2C HID devices:\n└─I2C Device 1 [] : \n"
769
+ )
770
+
771
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
772
+ @patch(
773
+ "amd_debug.prerequisites.open",
774
+ new_callable=unittest.mock.mock_open,
775
+ read_data=b"\x00" * 0x70 + b"\x20\x00\x00\x00",
776
+ )
777
+ @patch("amd_debug.prerequisites.struct.unpack", return_value=(0x00200000,))
778
+ def test_check_fadt_supports_low_power_idle(
779
+ self, mock_unpack, mock_open, mock_path_exists
780
+ ):
781
+ """Test check_fadt when ACPI FADT supports Low-power S0 idle"""
782
+ self.mock_kernel_log.match_line.return_value = False
783
+ result = self.validator.check_fadt()
784
+ self.assertTrue(result)
785
+ self.mock_db.record_prereq.assert_called_with(
786
+ "ACPI FADT supports Low-power S0 idle", "✅"
787
+ )
788
+
789
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
790
+ @patch(
791
+ "amd_debug.prerequisites.open",
792
+ new_callable=unittest.mock.mock_open,
793
+ read_data=b"\x00" * 0x70 + b"\x00\x00\x00\x00",
794
+ )
795
+ @patch("amd_debug.prerequisites.struct.unpack", return_value=(0x00000000,))
796
+ def test_check_fadt_does_not_support_low_power_idle(
797
+ self, mock_unpack, mock_open, mock_path_exists
798
+ ):
799
+ """Test check_fadt when ACPI FADT does not support Low-power S0 idle"""
800
+ self.mock_kernel_log.match_line.return_value = False
801
+ result = self.validator.check_fadt()
802
+ self.assertFalse(result)
803
+ self.assertTrue(any(isinstance(f, FadtWrong) for f in self.validator.failures))
804
+
805
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
806
+ def test_check_fadt_file_not_found(self, mock_path_exists):
807
+ """Test check_fadt when FADT file is not found"""
808
+ self.mock_kernel_log.match_line.return_value = False
809
+ result = self.validator.check_fadt()
810
+ self.assertTrue(result)
811
+ self.mock_db.record_prereq.assert_called_with("FADT check unavailable", "🚦")
812
+
813
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
814
+ @patch("amd_debug.prerequisites.open", side_effect=PermissionError)
815
+ def test_check_fadt_permission_error(self, mock_open, mock_path_exists):
816
+ """Test check_fadt when there is a permission error accessing the FADT file"""
817
+ self.mock_kernel_log.match_line.return_value = False
818
+ result = self.validator.check_fadt()
819
+ self.assertTrue(result)
820
+ self.mock_db.record_prereq.assert_called_with("FADT check unavailable", "🚦")
821
+
822
+ def test_check_fadt_kernel_log_match(self):
823
+ """Test check_fadt when kernel log contains the required message"""
824
+ self.mock_kernel_log.match_line.return_value = True
825
+ result = self.validator.check_fadt()
826
+ self.assertTrue(result)
827
+ self.mock_db.record_prereq.assert_called_with(
828
+ "ACPI FADT supports Low-power S0 idle", "✅"
829
+ )
830
+
831
+ @patch(
832
+ "amd_debug.prerequisites.open",
833
+ new_callable=unittest.mock.mock_open,
834
+ read_data=b"\x00" * 0x70 + b"\x00\x00\x00\x00",
835
+ )
836
+ def test_check_fadt_no_kernel_log(self, _mock_open):
837
+ """Test check_fadt when kernel log is not available"""
838
+ self.validator.kernel_log = None
839
+ result = self.validator.check_fadt()
840
+ self.assertFalse(result)
841
+ self.mock_db.record_prereq.assert_called_with(
842
+ "ACPI FADT doesn't support Low-power S0 idle", "❌"
843
+ )
844
+
845
+ @patch("amd_debug.prerequisites.read_file")
846
+ def test_get_cpu_vendor_all_fields_present(self, mock_read_file):
847
+ """Test get_cpu_vendor when all fields are present in /proc/cpuinfo"""
848
+ mock_read_file.return_value = (
849
+ "vendor_id\t: AuthenticAMD\n"
850
+ "cpu family\t: 23\n"
851
+ "model\t\t: 1\n"
852
+ "model name\t: AMD Ryzen 7 3700X\n"
853
+ )
854
+ vendor = self.validator.get_cpu_vendor()
855
+ self.assertEqual(vendor, "AuthenticAMD")
856
+ self.assertEqual(self.validator.cpu_family, 23)
857
+ self.assertEqual(self.validator.cpu_model, 1)
858
+ self.assertEqual(self.validator.cpu_model_string, "AMD Ryzen 7 3700X")
859
+ self.mock_db.record_prereq.assert_called_with(
860
+ "AMD Ryzen 7 3700X (family 17 model 1)", "💻"
861
+ )
862
+
863
+ @patch("amd_debug.prerequisites.read_file")
864
+ def test_get_cpu_vendor_missing_model_name(self, mock_read_file):
865
+ """Test get_cpu_vendor when model name is missing in /proc/cpuinfo"""
866
+ mock_read_file.return_value = (
867
+ "vendor_id\t: AuthenticAMD\n" "cpu family\t: 23\n" "model\t\t: 1\n"
868
+ )
869
+ vendor = self.validator.get_cpu_vendor()
870
+ self.assertEqual(vendor, "AuthenticAMD")
871
+ self.assertEqual(self.validator.cpu_family, 23)
872
+ self.assertEqual(self.validator.cpu_model, 1)
873
+ self.assertIsNone(self.validator.cpu_model_string)
874
+ self.mock_db.record_prereq.assert_not_called()
875
+
876
+ @patch("amd_debug.prerequisites.read_file")
877
+ def test_get_cpu_vendor_missing_vendor_id(self, mock_read_file):
878
+ """Test get_cpu_vendor when vendor_id is missing in /proc/cpuinfo"""
879
+ mock_read_file.return_value = (
880
+ "cpu family\t: 23\n" "model\t\t: 1\n" "model name\t: AMD Ryzen 7 3700X\n"
881
+ )
882
+ vendor = self.validator.get_cpu_vendor()
883
+ self.assertEqual(vendor, "")
884
+ self.assertEqual(self.validator.cpu_family, 23)
885
+ self.assertEqual(self.validator.cpu_model, 1)
886
+ self.assertEqual(self.validator.cpu_model_string, "AMD Ryzen 7 3700X")
887
+ self.mock_db.record_prereq.assert_called_with(
888
+ "AMD Ryzen 7 3700X (family 17 model 1)", "💻"
889
+ )
890
+
891
+ @patch("amd_debug.prerequisites.read_file")
892
+ def test_get_cpu_vendor_missing_cpu_family(self, mock_read_file):
893
+ """Test get_cpu_vendor when cpu family is missing in /proc/cpuinfo"""
894
+ mock_read_file.return_value = (
895
+ "vendor_id\t: AuthenticAMD\n"
896
+ "model\t\t: 1\n"
897
+ "model name\t: AMD Ryzen 7 3700X\n"
898
+ )
899
+ vendor = self.validator.get_cpu_vendor()
900
+ self.assertEqual(vendor, "AuthenticAMD")
901
+ self.assertIsNone(self.validator.cpu_family)
902
+ self.assertEqual(self.validator.cpu_model, 1)
903
+ self.assertEqual(self.validator.cpu_model_string, "AMD Ryzen 7 3700X")
904
+ self.mock_db.record_prereq.assert_not_called()
905
+
906
+ @patch("amd_debug.prerequisites.read_file")
907
+ def test_get_cpu_vendor_missing_model(self, mock_read_file):
908
+ """Test get_cpu_vendor when model is missing in /proc/cpuinfo"""
909
+ mock_read_file.return_value = (
910
+ "vendor_id\t: AuthenticAMD\n"
911
+ "cpu family\t: 23\n"
912
+ "model name\t: AMD Ryzen 7 3700X\n"
913
+ )
914
+ vendor = self.validator.get_cpu_vendor()
915
+ self.assertEqual(vendor, "AuthenticAMD")
916
+ self.assertEqual(self.validator.cpu_family, 23)
917
+ self.assertIsNone(self.validator.cpu_model)
918
+ self.assertEqual(self.validator.cpu_model_string, "AMD Ryzen 7 3700X")
919
+ self.mock_db.record_prereq.assert_not_called()
920
+
921
+ @patch("amd_debug.prerequisites.read_file")
922
+ def test_get_cpu_vendor_empty_cpuinfo(self, mock_read_file):
923
+ """Test get_cpu_vendor when /proc/cpuinfo is empty"""
924
+ mock_read_file.return_value = ""
925
+ vendor = self.validator.get_cpu_vendor()
926
+ self.assertEqual(vendor, "")
927
+ self.assertIsNone(self.validator.cpu_family)
928
+ self.assertIsNone(self.validator.cpu_model)
929
+ self.assertIsNone(self.validator.cpu_model_string)
930
+ self.mock_db.record_prereq.assert_not_called()
931
+
932
+ def test_check_usb4_driver_missing(self):
933
+ """Test check_usb4 when the thunderbolt driver is missing"""
934
+ self.mock_pyudev.list_devices.return_value = [
935
+ MagicMock(
936
+ properties={
937
+ "PCI_SLOT_NAME": "0000:00:1d.0",
938
+ "DRIVER": None,
939
+ }
940
+ )
941
+ ]
942
+ result = self.validator.check_usb4()
943
+ self.assertFalse(result)
944
+ self.assertTrue(
945
+ any(isinstance(f, MissingThunderbolt) for f in self.validator.failures)
946
+ )
947
+ self.mock_db.record_prereq.assert_called_with(
948
+ "USB4 driver `thunderbolt` missing", "❌"
949
+ )
950
+
951
+ def test_check_usb4_driver_present(self):
952
+ """Test check_usb4 when the thunderbolt driver is present"""
953
+ self.mock_pyudev.list_devices.return_value = [
954
+ MagicMock(
955
+ properties={
956
+ "PCI_SLOT_NAME": "0000:00:1d.0",
957
+ "DRIVER": "thunderbolt",
958
+ }
959
+ )
960
+ ]
961
+ result = self.validator.check_usb4()
962
+ self.assertTrue(result)
963
+ self.mock_db.record_prereq.assert_called_with(
964
+ "USB4 driver `thunderbolt` bound to 0000:00:1d.0", "✅"
965
+ )
966
+
967
+ def test_check_usb4_no_devices(self):
968
+ """Test check_usb4 when no USB4 devices are found"""
969
+ self.mock_pyudev.list_devices.return_value = []
970
+ result = self.validator.check_usb4()
971
+ self.assertTrue(result)
972
+ self.mock_db.record_prereq.assert_not_called()
973
+
974
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
975
+ def test_capture_smbios_not_setup(self, mock_path_exists):
976
+ """Test capture_smbios when DMI data is not set up"""
977
+ self.validator.capture_smbios()
978
+ self.mock_db.record_prereq.assert_called_with("DMI data was not setup", "🚦")
979
+ self.assertTrue(
980
+ any(isinstance(f, DmiNotSetup) for f in self.validator.failures)
981
+ )
982
+
983
+ @patch("amd_debug.prerequisites.os.walk")
984
+ @patch("amd_debug.prerequisites.read_file")
985
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
986
+ def test_capture_smbios_success(
987
+ self, mock_path_exists, mock_read_file, mock_os_walk
988
+ ):
989
+ """Test capture_smbios when DMI data is successfully captured"""
990
+ mock_os_walk.return_value = [
991
+ (
992
+ "/sys/class/dmi/id",
993
+ [],
994
+ ["sys_vendor", "product_name", "product_family", "chassis_type"],
995
+ )
996
+ ]
997
+ mock_read_file.side_effect = lambda path: {
998
+ "/sys/class/dmi/id/sys_vendor": "MockVendor",
999
+ "/sys/class/dmi/id/product_name": "MockProduct",
1000
+ "/sys/class/dmi/id/product_family": "MockFamily",
1001
+ "/sys/class/dmi/id/chassis_type": "Desktop",
1002
+ }.get(path, "")
1003
+ result = self.validator.capture_smbios()
1004
+ self.assertTrue(result)
1005
+ self.mock_db.record_prereq.assert_called_with(
1006
+ "MockVendor MockProduct (MockFamily)", "💻"
1007
+ )
1008
+ self.mock_db.record_debug.assert_called_with(
1009
+ "DMI data:\nchassis_type: Desktop\n"
1010
+ )
1011
+
1012
+ @patch("amd_debug.prerequisites.os.walk")
1013
+ @patch("amd_debug.prerequisites.read_file")
1014
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1015
+ def test_capture_smbios_filtered_keys(
1016
+ self, _mock_path_exists, mock_read_file, mock_os_walk
1017
+ ):
1018
+ """Test capture_smbios when filtered keys are present"""
1019
+ mock_os_walk.return_value = [
1020
+ (
1021
+ "/sys/class/dmi/id",
1022
+ [],
1023
+ ["sys_vendor", "product_name", "product_family", "product_serial"],
1024
+ )
1025
+ ]
1026
+ mock_read_file.side_effect = lambda path: {
1027
+ "/sys/class/dmi/id/sys_vendor": "MockVendor",
1028
+ "/sys/class/dmi/id/product_name": "MockProduct",
1029
+ "/sys/class/dmi/id/product_family": "MockFamily",
1030
+ "/sys/class/dmi/id/product_serial": "12345",
1031
+ }.get(path, "")
1032
+ result = self.validator.capture_smbios()
1033
+ self.assertTrue(result)
1034
+ self.mock_db.record_prereq.assert_called_with(
1035
+ "MockVendor MockProduct (MockFamily)", "💻"
1036
+ )
1037
+ self.mock_db.record_debug.assert_called_with("DMI data:\n")
1038
+
1039
+ @patch("amd_debug.prerequisites.os.walk")
1040
+ @patch("amd_debug.prerequisites.read_file")
1041
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1042
+ def test_capture_smbios_missing_keys(
1043
+ self, _mock_path_exists, mock_read_file, mock_os_walk
1044
+ ):
1045
+ """Test capture_smbios when required keys are missing"""
1046
+ mock_os_walk.return_value = [("/sys/class/dmi/id", [], ["chassis_type"])]
1047
+ mock_read_file.side_effect = lambda path: {
1048
+ "/sys/class/dmi/id/chassis_type": "Desktop",
1049
+ }.get(path, "")
1050
+ result = self.validator.capture_smbios()
1051
+ self.assertTrue(
1052
+ any(isinstance(f, DmiNotSetup) for f in self.validator.failures)
1053
+ )
1054
+ self.assertFalse(result)
1055
+
1056
+ @patch("amd_debug.prerequisites.os.path.exists")
1057
+ @patch("amd_debug.prerequisites.read_file")
1058
+ def test_check_lps0_enabled(self, mock_read_file, mock_path_exists):
1059
+ """Test check_lps0 when LPS0 is enabled"""
1060
+ mock_path_exists.return_value = True
1061
+ mock_read_file.return_value = "N"
1062
+ result = self.validator.check_lps0()
1063
+ self.assertTrue(result)
1064
+ self.mock_db.record_prereq.assert_called_with("LPS0 _DSM enabled", "✅")
1065
+
1066
+ @patch("amd_debug.prerequisites.os.path.exists")
1067
+ @patch("amd_debug.prerequisites.read_file")
1068
+ def test_check_lps0_disabled(self, mock_read_file, mock_path_exists):
1069
+ """Test check_lps0 when LPS0 is disabled"""
1070
+ mock_path_exists.return_value = True
1071
+ mock_read_file.return_value = "Y"
1072
+ result = self.validator.check_lps0()
1073
+ self.assertFalse(result)
1074
+ self.mock_db.record_prereq.assert_called_with("LPS0 _DSM disabled", "❌")
1075
+
1076
+ @patch("amd_debug.prerequisites.os.path.exists")
1077
+ def test_check_lps0_not_found(self, mock_path_exists):
1078
+ """Test check_lps0 when LPS0 parameter is not found"""
1079
+ mock_path_exists.return_value = False
1080
+ result = self.validator.check_lps0()
1081
+ self.assertFalse(result)
1082
+ self.mock_db.record_prereq.assert_called_with("LPS0 _DSM not found", "👀")
1083
+
1084
+ @patch("amd_debug.prerequisites.os.path.exists")
1085
+ @patch(
1086
+ "builtins.open",
1087
+ new_callable=unittest.mock.mock_open,
1088
+ read_data="ignore_wake_value",
1089
+ )
1090
+ def test_capture_disabled_pins_with_parameters(self, _mock_open, mock_path_exists):
1091
+ """Test capture_disabled_pins when parameters are present and configured"""
1092
+ mock_path_exists.side_effect = (
1093
+ lambda path: "ignore_wake" in path or "ignore_interrupt" in path
1094
+ )
1095
+ self.validator.capture_disabled_pins()
1096
+ self.mock_db.record_debug.assert_called_with(
1097
+ "Disabled pins:\n/sys/module/gpiolib_acpi/parameters/ignore_wake is configured to ignore_wake_value\n/sys/module/gpiolib_acpi/parameters/ignore_interrupt is configured to ignore_wake_value\n"
1098
+ )
1099
+
1100
+ @patch("amd_debug.prerequisites.os.path.exists")
1101
+ @patch("builtins.open", new_callable=unittest.mock.mock_open, read_data="(null)")
1102
+ def test_capture_disabled_pins_with_null_values(self, _mock_open, mock_path_exists):
1103
+ mock_path_exists.side_effect = (
1104
+ lambda path: "ignore_wake" in path or "ignore_interrupt" in path
1105
+ )
1106
+ self.validator.capture_disabled_pins()
1107
+ self.mock_db.record_debug.assert_not_called()
1108
+
1109
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1110
+ def test_capture_disabled_pins_no_parameters(self, _mock_path_exists):
1111
+ """Test capture_disabled_pins when parameters are not present"""
1112
+ self.validator.capture_disabled_pins()
1113
+ self.mock_db.record_debug.assert_not_called()
1114
+
1115
+ @patch("amd_debug.prerequisites.os.listdir")
1116
+ @patch("amd_debug.prerequisites.os.path.isdir")
1117
+ @patch("amd_debug.prerequisites.WakeIRQ")
1118
+ def test_capture_irq_with_irqs(self, MockWakeIRQ, mock_isdir, mock_listdir):
1119
+ """Test capture_irq when IRQ directories are present"""
1120
+ mock_listdir.return_value = ["1", "2", "3"]
1121
+ mock_isdir.side_effect = lambda path: path.endswith(("1", "2", "3"))
1122
+ MockWakeIRQ.side_effect = lambda irq, pyudev: f"WakeIRQ-{irq}"
1123
+
1124
+ result = self.validator.capture_irq()
1125
+
1126
+ self.assertTrue(result)
1127
+ self.assertEqual(
1128
+ self.validator.irqs,
1129
+ [[1, "WakeIRQ-1"], [2, "WakeIRQ-2"], [3, "WakeIRQ-3"]],
1130
+ )
1131
+ self.mock_db.record_debug.assert_any_call("Interrupts")
1132
+ self.mock_db.record_debug.assert_any_call("│ 1: WakeIRQ-1")
1133
+ self.mock_db.record_debug.assert_any_call("│ 2: WakeIRQ-2")
1134
+ self.mock_db.record_debug.assert_any_call("└─3: WakeIRQ-3")
1135
+
1136
+ @patch("amd_debug.prerequisites.os.listdir")
1137
+ @patch("amd_debug.prerequisites.os.path.isdir")
1138
+ def test_capture_irq_no_irqs(self, mock_isdir, mock_listdir):
1139
+ """Test capture_irq when no IRQ directories are present"""
1140
+ mock_listdir.return_value = []
1141
+ mock_isdir.return_value = False
1142
+
1143
+ result = self.validator.capture_irq()
1144
+
1145
+ self.assertTrue(result)
1146
+ self.assertEqual(self.validator.irqs, [])
1147
+ self.mock_db.record_debug.assert_called_with("Interrupts")
1148
+
1149
+ @patch("amd_debug.prerequisites.os.listdir")
1150
+ @patch("amd_debug.prerequisites.os.path.isdir")
1151
+ @patch("amd_debug.prerequisites.WakeIRQ")
1152
+ def test_capture_irq_mixed_entries(self, mock_wake_irq, mock_isdir, mock_listdir):
1153
+ """Test capture_irq with mixed valid and invalid IRQ directories"""
1154
+ mock_listdir.return_value = ["1", "invalid", "2"]
1155
+ mock_isdir.side_effect = lambda path: path.endswith(("1", "2"))
1156
+ mock_wake_irq.side_effect = lambda irq, pyudev: f"WakeIRQ-{irq}"
1157
+
1158
+ result = self.validator.capture_irq()
1159
+
1160
+ self.assertTrue(result)
1161
+ self.assertEqual(
1162
+ self.validator.irqs,
1163
+ [[1, "WakeIRQ-1"], [2, "WakeIRQ-2"]],
1164
+ )
1165
+ self.mock_db.record_debug.assert_any_call("Interrupts")
1166
+ self.mock_db.record_debug.assert_any_call("│ 1: WakeIRQ-1")
1167
+ self.mock_db.record_debug.assert_any_call("└─2: WakeIRQ-2")
1168
+
1169
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1170
+ @patch("builtins.open", new_callable=unittest.mock.mock_open)
1171
+ def test_check_permissions_success(self, _mock_open, _mock_path_exists):
1172
+ """Test check_permissions when the user has write permissions"""
1173
+ result = self.validator.check_permissions()
1174
+ self.assertTrue(result)
1175
+ self.mock_db.record_prereq.assert_not_called()
1176
+
1177
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1178
+ @patch("builtins.open", side_effect=PermissionError)
1179
+ def test_check_permissions_permission_error(self, _mock_open, _mock_path_exists):
1180
+ """Test check_permissions when the user lacks write permissions"""
1181
+ result = self.validator.check_permissions()
1182
+ self.assertFalse(result)
1183
+
1184
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1185
+ @patch("builtins.open", side_effect=FileNotFoundError)
1186
+ def test_check_permissions_file_not_found(self, _mock_open, _mock_path_exists):
1187
+ """Test check_permissions when the /sys/power/state file is not found"""
1188
+ result = self.validator.check_permissions()
1189
+ self.assertFalse(result)
1190
+ self.mock_db.record_prereq.assert_called_with(
1191
+ "Kernel doesn't support power management", "❌"
1192
+ )
1193
+
1194
+ @patch("amd_debug.prerequisites.read_file")
1195
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1196
+ def test_check_pinctrl_amd_driver_loaded(self, mock_path_exists, mock_read_file):
1197
+ """Test check_pinctrl_amd when the driver is loaded and debug information is available"""
1198
+ mock_read_file.return_value = (
1199
+ "trigger\n" "edge\n" "level\n" "WAKE_INT_MASTER_REG: 8000\n"
1200
+ )
1201
+ self.mock_pyudev.list_devices.return_value = [
1202
+ MagicMock(properties={"DRIVER": "amd_gpio"})
1203
+ ]
1204
+
1205
+ result = self.validator.check_pinctrl_amd()
1206
+ self.assertTrue(result)
1207
+ self.mock_db.record_prereq.assert_called_with(
1208
+ "GPIO driver `pinctrl_amd` available", "✅"
1209
+ )
1210
+ self.mock_db.record_debug.assert_called_with("trigger\nedge\nlevel\n")
1211
+
1212
+ @patch("amd_debug.prerequisites.read_file")
1213
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1214
+ def test_check_pinctrl_amd_driver_loaded_with_permission_error(
1215
+ self, mock_path_exists, mock_read_file
1216
+ ):
1217
+ """Test check_pinctrl_amd when the driver is loaded but debug file cannot be read due to permission error"""
1218
+ mock_read_file.side_effect = PermissionError
1219
+ self.mock_pyudev.list_devices.return_value = [
1220
+ MagicMock(properties={"DRIVER": "amd_gpio"})
1221
+ ]
1222
+
1223
+ result = self.validator.check_pinctrl_amd()
1224
+ self.assertTrue(result)
1225
+ self.mock_db.record_prereq.assert_called_with(
1226
+ "GPIO driver `pinctrl_amd` available", "✅"
1227
+ )
1228
+ self.mock_db.record_debug.assert_called_with(
1229
+ "Unable to capture /sys/kernel/debug/gpio"
1230
+ )
1231
+
1232
+ @patch("amd_debug.prerequisites.read_file")
1233
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1234
+ def test_check_pinctrl_amd_unserviced_gpio(self, _mock_path_exists, mock_read_file):
1235
+ """Test check_pinctrl_amd when unserviced GPIO is detected"""
1236
+ mock_read_file.return_value = "🔥"
1237
+ self.mock_pyudev.list_devices.return_value = [
1238
+ MagicMock(properties={"DRIVER": "amd_gpio"})
1239
+ ]
1240
+
1241
+ result = self.validator.check_pinctrl_amd()
1242
+ self.assertFalse(result)
1243
+ self.assertTrue(
1244
+ any(isinstance(f, UnservicedGpio) for f in self.validator.failures)
1245
+ )
1246
+
1247
+ @patch("amd_debug.prerequisites.read_file")
1248
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1249
+ def test_check_pinctrl_amd_no_debug_info(self, mock_path_exists, mock_read_file):
1250
+ """Test check_pinctrl_amd when the driver is loaded but no debug information is available"""
1251
+ mock_read_file.return_value = ""
1252
+ self.mock_pyudev.list_devices.return_value = [
1253
+ MagicMock(properties={"DRIVER": "amd_gpio"})
1254
+ ]
1255
+
1256
+ result = self.validator.check_pinctrl_amd()
1257
+ self.assertTrue(result)
1258
+ self.mock_db.record_prereq.assert_called_with(
1259
+ "GPIO driver `pinctrl_amd` available", "✅"
1260
+ )
1261
+ self.mock_db.record_debug.assert_not_called()
1262
+
1263
+ def test_check_pinctrl_amd_driver_not_loaded(self):
1264
+ """Test check_pinctrl_amd when the driver is not loaded"""
1265
+ self.mock_pyudev.list_devices.return_value = []
1266
+
1267
+ result = self.validator.check_pinctrl_amd()
1268
+ self.assertFalse(result)
1269
+ self.mock_db.record_prereq.assert_called_with(
1270
+ "GPIO driver `pinctrl_amd` not loaded", "❌"
1271
+ )
1272
+
1273
+ @patch("amd_debug.prerequisites.os.path.exists")
1274
+ @patch("amd_debug.prerequisites.read_file")
1275
+ def test_check_asus_rog_ally_mcu_version_too_old(
1276
+ self, mock_read_file, mock_path_exists
1277
+ ):
1278
+ """Test check_asus_rog_ally when MCU version is too old"""
1279
+ mock_path_exists.side_effect = lambda p: "mcu_version" in p
1280
+ mock_read_file.side_effect = lambda p: "318" if "mcu_version" in p else ""
1281
+ self.mock_pyudev.list_devices.side_effect = [
1282
+ [MagicMock(sys_path="/sys/devices/hid1", properties={"HID_ID": "1ABE"})],
1283
+ [],
1284
+ ]
1285
+
1286
+ result = self.validator.check_asus_rog_ally()
1287
+ self.assertFalse(result)
1288
+ self.mock_db.record_prereq.assert_called_with(
1289
+ "ROG Ally MCU firmware too old", "❌"
1290
+ )
1291
+ self.assertTrue(
1292
+ any(isinstance(f, RogAllyOldMcu) for f in self.validator.failures)
1293
+ )
1294
+
1295
+ @patch("amd_debug.prerequisites.os.path.exists")
1296
+ @patch("amd_debug.prerequisites.read_file")
1297
+ def test_check_asus_rog_ally_mcu_version_valid(
1298
+ self, mock_read_file, mock_path_exists
1299
+ ):
1300
+ """Test check_asus_rog_ally when MCU version is valid"""
1301
+ mock_path_exists.side_effect = lambda p: "mcu_version" in p
1302
+ mock_read_file.side_effect = lambda p: "320" if "mcu_version" in p else ""
1303
+ self.mock_pyudev.list_devices.side_effect = [
1304
+ [MagicMock(sys_path="/sys/devices/hid1", properties={"HID_ID": "1ABE"})],
1305
+ [],
1306
+ ]
1307
+
1308
+ result = self.validator.check_asus_rog_ally()
1309
+ self.assertTrue(result)
1310
+
1311
+ @patch("amd_debug.prerequisites.os.path.exists")
1312
+ @patch("amd_debug.prerequisites.read_file")
1313
+ def test_check_asus_rog_ally_mcu_powersave_disabled(
1314
+ self, mock_read_file, mock_path_exists
1315
+ ):
1316
+ """Test check_asus_rog_ally when MCU powersave is disabled"""
1317
+ mock_path_exists.side_effect = lambda p: "current_value" in p
1318
+ mock_read_file.side_effect = lambda p: "0" if "current_value" in p else ""
1319
+ self.mock_pyudev.list_devices.side_effect = [
1320
+ [],
1321
+ [MagicMock(sys_path="/sys/devices/firmware1")],
1322
+ ]
1323
+
1324
+ result = self.validator.check_asus_rog_ally()
1325
+ self.assertFalse(result)
1326
+ self.mock_db.record_prereq.assert_called_with(
1327
+ "Rog Ally doesn't have MCU powersave enabled", "❌"
1328
+ )
1329
+ self.assertTrue(
1330
+ any(isinstance(f, RogAllyMcuPowerSave) for f in self.validator.failures)
1331
+ )
1332
+
1333
+ @patch("amd_debug.prerequisites.os.path.exists")
1334
+ @patch("amd_debug.prerequisites.read_file")
1335
+ def test_check_asus_rog_ally_mcu_powersave_enabled(
1336
+ self, mock_read_file, mock_path_exists
1337
+ ):
1338
+ """Test check_asus_rog_ally when MCU powersave is enabled"""
1339
+ mock_path_exists.side_effect = lambda p: "current_value" in p
1340
+ mock_read_file.side_effect = lambda p: "1" if "current_value" in p else ""
1341
+ self.mock_pyudev.list_devices.side_effect = [
1342
+ [],
1343
+ [MagicMock(sys_path="/sys/devices/firmware1")],
1344
+ ]
1345
+
1346
+ result = self.validator.check_asus_rog_ally()
1347
+ self.assertTrue(result)
1348
+
1349
+ @patch("amd_debug.prerequisites.os.path.exists")
1350
+ @patch("amd_debug.prerequisites.read_file")
1351
+ def test_check_asus_rog_ally_no_devices(self, _mock_read_file, mock_path_exists):
1352
+ """Test check_asus_rog_ally when no devices are found"""
1353
+ mock_path_exists.return_value = False
1354
+ self.mock_pyudev.list_devices.side_effect = [[], []]
1355
+
1356
+ result = self.validator.check_asus_rog_ally()
1357
+ self.assertTrue(result)
1358
+
1359
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1360
+ def test_check_network_wol_supported_and_enabled(self, mock_check_output):
1361
+ """Test check_network when WoL is supported and enabled"""
1362
+ self.mock_pyudev.list_devices.return_value = [
1363
+ MagicMock(properties={"INTERFACE": "eth0"})
1364
+ ]
1365
+ mock_check_output.return_value = (
1366
+ "Supports Wake-on: g\n" "Wake-on: g\n"
1367
+ ).encode("utf-8")
1368
+
1369
+ result = self.validator.check_network()
1370
+ self.assertTrue(result)
1371
+ self.mock_db.record_debug.assert_called_with("eth0 supports WoL")
1372
+ self.mock_db.record_prereq.assert_called_with("eth0 has WoL enabled", "✅")
1373
+
1374
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1375
+ def test_check_network_wol_supported_but_disabled(self, mock_check_output):
1376
+ """Test check_network when WoL is supported but disabled"""
1377
+ self.mock_pyudev.list_devices.return_value = [
1378
+ MagicMock(properties={"INTERFACE": "eth0"})
1379
+ ]
1380
+ mock_check_output.return_value = (
1381
+ "Supports Wake-on: g\n" "Wake-on: d\n"
1382
+ ).encode("utf-8")
1383
+
1384
+ result = self.validator.check_network()
1385
+ self.assertTrue(result)
1386
+ self.mock_db.record_debug.assert_called_with("eth0 supports WoL")
1387
+ self.mock_db.record_prereq.assert_called_with(
1388
+ "Platform may have low hardware sleep residency with Wake-on-lan disabled. Run `ethtool -s eth0 wol g` to enable it if necessary.",
1389
+ "🚦",
1390
+ )
1391
+
1392
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1393
+ def test_check_network_wol_not_supported(self, mock_check_output):
1394
+ """Test check_network when WoL is not supported"""
1395
+ self.mock_pyudev.list_devices.return_value = [
1396
+ MagicMock(properties={"INTERFACE": "eth0"})
1397
+ ]
1398
+ mock_check_output.return_value = ("Supports Wake-on: d\n").encode("utf-8")
1399
+
1400
+ result = self.validator.check_network()
1401
+ self.assertTrue(result)
1402
+ self.mock_db.record_debug.assert_called_with("eth0 doesn't support WoL (d)")
1403
+
1404
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1405
+ def test_check_network_no_devices(self, mock_check_output):
1406
+ """Test check_network when no network devices are found"""
1407
+ self.mock_pyudev.list_devices.return_value = []
1408
+
1409
+ result = self.validator.check_network()
1410
+ self.assertTrue(result)
1411
+ self.mock_db.record_debug.assert_not_called()
1412
+ self.mock_db.record_prereq.assert_not_called()
1413
+
1414
+ @patch(
1415
+ "amd_debug.prerequisites.subprocess.check_output",
1416
+ side_effect=subprocess.CalledProcessError(1, "ethtool"),
1417
+ )
1418
+ def test_check_network_ethtool_error(self, mock_check_output):
1419
+ """Test check_network when ethtool command fails"""
1420
+ self.mock_pyudev.list_devices.return_value = [
1421
+ MagicMock(properties={"INTERFACE": "eth0"})
1422
+ ]
1423
+
1424
+ with self.assertRaises(subprocess.CalledProcessError):
1425
+ self.validator.check_network()
1426
+ self.mock_db.record_debug.assert_not_called()
1427
+ self.mock_db.record_prereq.assert_not_called()
1428
+
1429
+ @patch("amd_debug.prerequisites.version.parse")
1430
+ def test_check_amd_cpu_hpet_wa_family_17_model_68(self, mock_version_parse):
1431
+ """Test check_amd_cpu_hpet_wa for family 0x17, model 0x68"""
1432
+ self.validator.cpu_family = 0x17
1433
+ self.validator.cpu_model = 0x68
1434
+ result = self.validator.check_amd_cpu_hpet_wa()
1435
+ self.assertTrue(result)
1436
+ self.mock_db.record_prereq.assert_called_with(
1437
+ "Timer based wakeup doesn't work properly for your ASIC/firmware, please manually wake the system",
1438
+ "🚦",
1439
+ )
1440
+
1441
+ @patch("amd_debug.prerequisites.version.parse")
1442
+ def test_check_amd_cpu_hpet_wa_family_17_model_60(self, mock_version_parse):
1443
+ """Test check_amd_cpu_hpet_wa for family 0x17, model 0x60"""
1444
+ self.validator.cpu_family = 0x17
1445
+ self.validator.cpu_model = 0x60
1446
+ result = self.validator.check_amd_cpu_hpet_wa()
1447
+ self.assertTrue(result)
1448
+ self.mock_db.record_prereq.assert_called_with(
1449
+ "Timer based wakeup doesn't work properly for your ASIC/firmware, please manually wake the system",
1450
+ "🚦",
1451
+ )
1452
+
1453
+ @patch("amd_debug.prerequisites.version.parse")
1454
+ def test_check_amd_cpu_hpet_wa_family_19_model_50_smu_version_low(
1455
+ self, mock_version_parse
1456
+ ):
1457
+ """Test check_amd_cpu_hpet_wa for family 0x19, model 0x50 with SMU version < 64.53.0"""
1458
+ self.validator.cpu_family = 0x19
1459
+ self.validator.cpu_model = 0x50
1460
+ self.validator.smu_version = "64.52.0"
1461
+ mock_version_parse.side_effect = lambda v: v if isinstance(v, str) else None
1462
+ result = self.validator.check_amd_cpu_hpet_wa()
1463
+ self.assertTrue(result)
1464
+ self.mock_db.record_prereq.assert_called_with(
1465
+ "Timer based wakeup doesn't work properly for your ASIC/firmware, please manually wake the system",
1466
+ "🚦",
1467
+ )
1468
+
1469
+ @patch("amd_debug.prerequisites.version.parse")
1470
+ def test_check_amd_cpu_hpet_wa_family_19_model_50_smu_version_high(
1471
+ self, mock_version_parse
1472
+ ):
1473
+ """Test check_amd_cpu_hpet_wa for family 0x19, model 0x50 with SMU version >= 64.53.0"""
1474
+ self.validator.cpu_family = 0x19
1475
+ self.validator.cpu_model = 0x50
1476
+ self.validator.smu_version = "64.53.0"
1477
+ mock_version_parse.side_effect = lambda v: v if isinstance(v, str) else None
1478
+ result = self.validator.check_amd_cpu_hpet_wa()
1479
+ self.assertTrue(result)
1480
+ self.mock_db.record_prereq.assert_not_called()
1481
+
1482
+ def test_check_amd_cpu_hpet_wa_family_19_non_matching_model(self):
1483
+ """Test check_amd_cpu_hpet_wa for family 0x19 with non-matching model"""
1484
+ self.validator.cpu_family = 0x19
1485
+ self.validator.cpu_model = 0x51
1486
+ result = self.validator.check_amd_cpu_hpet_wa()
1487
+ self.assertTrue(result)
1488
+ self.mock_db.record_prereq.assert_not_called()
1489
+
1490
+ def test_check_amd_cpu_hpet_wa_non_matching_family(self):
1491
+ """Test check_amd_cpu_hpet_wa for non-matching CPU family"""
1492
+ self.validator.cpu_family = 0x18
1493
+ self.validator.cpu_model = 0x68
1494
+ result = self.validator.check_amd_cpu_hpet_wa()
1495
+ self.assertTrue(result)
1496
+ self.mock_db.record_prereq.assert_not_called()
1497
+
1498
+ @patch("amd_debug.prerequisites.os.path.exists")
1499
+ def test_capture_linux_firmware_debug_files_exist(self, mock_path_exists):
1500
+ """Test capture_linux_firmware when debug files exist"""
1501
+ mock_path_exists.side_effect = lambda path: "amdgpu_firmware_info" in path
1502
+
1503
+ self.validator.distro = "ubuntu"
1504
+ self.validator.capture_linux_firmware()
1505
+
1506
+ self.mock_db.record_debug_file.assert_any_call(
1507
+ "/sys/kernel/debug/dri/0/amdgpu_firmware_info"
1508
+ )
1509
+ self.mock_db.record_debug_file.assert_any_call(
1510
+ "/sys/kernel/debug/dri/1/amdgpu_firmware_info"
1511
+ )
1512
+
1513
+ @patch("amd_debug.prerequisites.os.path.exists")
1514
+ def test_capture_linux_firmware_debug_files_missing(self, mock_path_exists):
1515
+ """Test capture_linux_firmware when debug files are missing"""
1516
+ mock_path_exists.return_value = False
1517
+
1518
+ self.validator.distro = "ubuntu"
1519
+ self.validator.capture_linux_firmware()
1520
+
1521
+ self.mock_db.record_debug_file.assert_not_called()
1522
+
1523
+ def test_check_wlan_no_devices(self):
1524
+ """Test check_wlan when no WLAN devices are found"""
1525
+ self.mock_pyudev.list_devices.return_value = []
1526
+ result = self.validator.check_wlan()
1527
+ self.assertTrue(result)
1528
+ self.mock_db.record_prereq.assert_not_called()
1529
+
1530
+ def test_check_wlan_missing_driver(self):
1531
+ """Test check_wlan when a WLAN device is missing a driver"""
1532
+ self.mock_pyudev.list_devices.return_value = [
1533
+ MagicMock(properties={"PCI_SLOT_NAME": "0000:00:1f.0", "DRIVER": None})
1534
+ ]
1535
+ result = self.validator.check_wlan()
1536
+ self.assertFalse(result)
1537
+ self.mock_db.record_prereq.assert_called_with(
1538
+ "WLAN device in 0000:00:1f.0 missing driver", "🚦"
1539
+ )
1540
+ self.assertTrue(
1541
+ any(isinstance(f, MissingDriver) for f in self.validator.failures)
1542
+ )
1543
+
1544
+ def test_check_wlan_with_driver(self):
1545
+ """Test check_wlan when a WLAN device has a driver"""
1546
+ self.mock_pyudev.list_devices.return_value = [
1547
+ MagicMock(properties={"PCI_SLOT_NAME": "0000:00:1f.0", "DRIVER": "iwlwifi"})
1548
+ ]
1549
+ result = self.validator.check_wlan()
1550
+ self.assertTrue(result)
1551
+ self.mock_db.record_prereq.assert_called_with(
1552
+ "WLAN driver `iwlwifi` bound to 0000:00:1f.0", "✅"
1553
+ )
1554
+
1555
+ def test_check_wlan_multiple_devices(self):
1556
+ """Test check_wlan with multiple WLAN devices"""
1557
+ self.mock_pyudev.list_devices.return_value = [
1558
+ MagicMock(
1559
+ properties={"PCI_SLOT_NAME": "0000:00:1f.0", "DRIVER": "iwlwifi"}
1560
+ ),
1561
+ MagicMock(properties={"PCI_SLOT_NAME": "0000:00:1f.1", "DRIVER": None}),
1562
+ ]
1563
+ result = self.validator.check_wlan()
1564
+ self.assertFalse(result)
1565
+ self.mock_db.record_prereq.assert_any_call(
1566
+ "WLAN driver `iwlwifi` bound to 0000:00:1f.0", "✅"
1567
+ )
1568
+ self.mock_db.record_prereq.assert_any_call(
1569
+ "WLAN device in 0000:00:1f.1 missing driver", "🚦"
1570
+ )
1571
+ self.assertTrue(
1572
+ any(isinstance(f, MissingDriver) for f in self.validator.failures)
1573
+ )
1574
+
1575
+ def test_check_usb3_no_devices(self):
1576
+ """Test check_usb3 when no USB3 devices are found"""
1577
+ self.mock_pyudev.list_devices.return_value = []
1578
+ result = self.validator.check_usb3()
1579
+ self.assertTrue(result)
1580
+ self.mock_db.record_prereq.assert_not_called()
1581
+
1582
+ def test_check_usb3_driver_missing(self):
1583
+ """Test check_usb3 when the xhci_hcd driver is missing"""
1584
+ self.mock_pyudev.list_devices.return_value = [
1585
+ MagicMock(
1586
+ properties={
1587
+ "PCI_SLOT_NAME": "0000:00:1d.0",
1588
+ "DRIVER": None,
1589
+ }
1590
+ )
1591
+ ]
1592
+ result = self.validator.check_usb3()
1593
+ self.assertFalse(result)
1594
+ self.assertTrue(
1595
+ any(isinstance(f, MissingXhciHcd) for f in self.validator.failures)
1596
+ )
1597
+ self.mock_db.record_prereq.assert_called_with(
1598
+ "USB3 controller for 0000:00:1d.0 not using `xhci_hcd` driver", "❌"
1599
+ )
1600
+
1601
+ def test_check_usb3_driver_present(self):
1602
+ """Test check_usb3 when the xhci_hcd driver is present"""
1603
+ self.mock_pyudev.list_devices.return_value = [
1604
+ MagicMock(
1605
+ properties={
1606
+ "PCI_SLOT_NAME": "0000:00:1d.0",
1607
+ "DRIVER": "xhci_hcd",
1608
+ }
1609
+ )
1610
+ ]
1611
+ result = self.validator.check_usb3()
1612
+ self.assertTrue(result)
1613
+ self.mock_db.record_prereq.assert_called_with(
1614
+ "USB3 driver `xhci_hcd` bound to 0000:00:1d.0", "✅"
1615
+ )
1616
+
1617
+ def test_check_usb3_multiple_devices(self):
1618
+ """Test check_usb3 with multiple USB3 devices"""
1619
+ self.mock_pyudev.list_devices.return_value = [
1620
+ MagicMock(
1621
+ properties={
1622
+ "PCI_SLOT_NAME": "0000:00:1d.0",
1623
+ "DRIVER": "xhci_hcd",
1624
+ }
1625
+ ),
1626
+ MagicMock(
1627
+ properties={
1628
+ "PCI_SLOT_NAME": "0000:00:1d.1",
1629
+ "DRIVER": None,
1630
+ }
1631
+ ),
1632
+ ]
1633
+ result = self.validator.check_usb3()
1634
+ self.assertFalse(result)
1635
+ self.mock_db.record_prereq.assert_any_call(
1636
+ "USB3 controller for 0000:00:1d.1 not using `xhci_hcd` driver", "❌"
1637
+ )
1638
+ self.assertTrue(
1639
+ any(isinstance(f, MissingXhciHcd) for f in self.validator.failures)
1640
+ )
1641
+
1642
+ def test_check_amd_pmc_driver_loaded(self):
1643
+ """Test check_amd_pmc when the driver is loaded"""
1644
+ self.mock_pyudev.list_devices.return_value = [
1645
+ MagicMock(
1646
+ sys_path="/sys/devices/platform/amd_pmc",
1647
+ properties={"DRIVER": "amd_pmc"},
1648
+ )
1649
+ ]
1650
+ with patch("amd_debug.prerequisites.os.path.exists", return_value=True), patch(
1651
+ "amd_debug.prerequisites.read_file",
1652
+ side_effect=["mock_version", "mock_program"],
1653
+ ):
1654
+ result = self.validator.check_amd_pmc()
1655
+ self.assertTrue(result)
1656
+ self.mock_db.record_prereq.assert_called_with(
1657
+ "PMC driver `amd_pmc` loaded (Program mock_program Firmware mock_version)",
1658
+ "✅",
1659
+ )
1660
+
1661
+ def test_check_amd_pmc_driver_loaded_timeout_error(self):
1662
+ """Test check_amd_pmc when a TimeoutError occurs while reading files"""
1663
+ self.mock_pyudev.list_devices.return_value = [
1664
+ MagicMock(
1665
+ sys_path="/sys/devices/platform/amd_pmc",
1666
+ properties={"DRIVER": "amd_pmc"},
1667
+ )
1668
+ ]
1669
+ with patch("amd_debug.prerequisites.os.path.exists", return_value=True), patch(
1670
+ "amd_debug.prerequisites.read_file", side_effect=TimeoutError
1671
+ ):
1672
+ result = self.validator.check_amd_pmc()
1673
+ self.assertFalse(result)
1674
+ self.mock_db.record_prereq.assert_called_with(
1675
+ "failed to communicate using `amd_pmc` driver", "❌"
1676
+ )
1677
+
1678
+ def test_check_amd_pmc_driver_not_loaded(self):
1679
+ """Test check_amd_pmc when the driver is not loaded"""
1680
+ self.mock_pyudev.list_devices.return_value = []
1681
+ result = self.validator.check_amd_pmc()
1682
+ self.assertFalse(result)
1683
+ self.assertTrue(
1684
+ any(isinstance(f, MissingAmdPmc) for f in self.validator.failures)
1685
+ )
1686
+ self.mock_db.record_prereq.assert_called_with(
1687
+ "PMC driver `amd_pmc` did not bind to any ACPI device", "❌"
1688
+ )
1689
+
1690
+ def test_check_amd_pmc_driver_loaded_no_version_info(self):
1691
+ """Test check_amd_pmc when the driver is loaded but version info is missing"""
1692
+ self.mock_pyudev.list_devices.return_value = [
1693
+ MagicMock(
1694
+ sys_path="/sys/devices/platform/amd_pmc",
1695
+ properties={"DRIVER": "amd_pmc"},
1696
+ )
1697
+ ]
1698
+ with patch("amd_debug.prerequisites.os.path.exists", return_value=False):
1699
+ result = self.validator.check_amd_pmc()
1700
+ self.assertTrue(result)
1701
+ self.mock_db.record_prereq.assert_called_with(
1702
+ "PMC driver `amd_pmc` loaded", "✅"
1703
+ )
1704
+
1705
+ @patch("amd_debug.prerequisites.minimum_kernel", return_value=True)
1706
+ def test_check_storage_new_kernel(self, _mock_minimum_kernel):
1707
+ """Test check_storage when kernel version >= 6.10"""
1708
+ self.mock_pyudev.list_devices.return_value = [
1709
+ MagicMock(properties={"PCI_SLOT_NAME": "0000:00:1f.0", "DRIVER": "nvme"})
1710
+ ]
1711
+ result = self.validator.check_storage()
1712
+ self.assertTrue(result)
1713
+ self.mock_db.record_debug.assert_called_with(
1714
+ "New enough kernel to avoid NVME check"
1715
+ )
1716
+
1717
+ def test_check_storage_no_kernel_log(self):
1718
+ """Test check_storage when kernel log is unavailable"""
1719
+ self.validator.kernel_log = None
1720
+ result = self.validator.check_storage()
1721
+ self.assertTrue(result)
1722
+ self.mock_db.record_prereq.assert_called_with(
1723
+ "Unable to test storage from kernel log", "🚦"
1724
+ )
1725
+
1726
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1727
+ def test_capture_edid_no_edid_data(self, mock_check_output):
1728
+ """Test capture_edid when no EDID data is found"""
1729
+ self.validator.display.get_edid = MagicMock(return_value={})
1730
+ result = self.validator.capture_edid()
1731
+ self.assertTrue(result)
1732
+ self.mock_db.record_debug.assert_called_with("No EDID data found")
1733
+ mock_check_output.assert_not_called()
1734
+
1735
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1736
+ def test_capture_edid_file_not_found(self, mock_check_output):
1737
+ """Test capture_edid when edid-decode is not installed"""
1738
+ self.validator.display.get_edid = MagicMock(
1739
+ return_value={"Monitor1": "/path/to/edid"}
1740
+ )
1741
+ mock_check_output.side_effect = FileNotFoundError
1742
+ result = self.validator.capture_edid()
1743
+ self.assertTrue(result)
1744
+ self.mock_db.record_prereq.assert_called_with(
1745
+ "Failed to capture EDID table", "👀"
1746
+ )
1747
+
1748
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1749
+ def test_capture_edid_subprocess_error(self, mock_check_output):
1750
+ """Test capture_edid when subprocess.check_output raises an error"""
1751
+ self.validator.display.get_edid = MagicMock(
1752
+ return_value={"Monitor1": "/path/to/edid"}
1753
+ )
1754
+ mock_check_output.side_effect = subprocess.CalledProcessError(
1755
+ returncode=1, cmd="edid-decode", output=b"Error decoding EDID"
1756
+ )
1757
+ result = self.validator.capture_edid()
1758
+ self.assertTrue(result)
1759
+ self.mock_db.record_prereq.assert_called_with(
1760
+ "Failed to capture EDID table", "👀"
1761
+ )
1762
+
1763
+ @patch("amd_debug.prerequisites.subprocess.check_output")
1764
+ def test_capture_edid_success(self, mock_check_output):
1765
+ """Test capture_edid when EDID data is successfully decoded"""
1766
+ self.validator.display.get_edid = MagicMock(
1767
+ return_value={"Monitor1": "/path/to/edid"}
1768
+ )
1769
+ mock_check_output.return_value = b"Decoded EDID data"
1770
+ result = self.validator.capture_edid()
1771
+ self.assertTrue(result)
1772
+ self.mock_db.record_debug.assert_called_with(
1773
+ apply_prefix_wrapper("EDID for Monitor1:", "Decoded EDID data")
1774
+ )
1775
+
1776
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1777
+ @patch("amd_debug.prerequisites.os.path.exists")
1778
+ @patch("amd_debug.prerequisites.read_file")
1779
+ def test_check_dpia_pg_dmcub_usb4_found(
1780
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1781
+ ):
1782
+ """Test check_dpia_pg_dmcub when USB4 routers are found"""
1783
+ usb4_device = MagicMock()
1784
+ self.mock_pyudev.list_devices.side_effect = [
1785
+ [usb4_device], # First call: USB4 present
1786
+ ]
1787
+ result = self.validator.check_dpia_pg_dmcub()
1788
+ self.assertTrue(result)
1789
+ self.mock_db.record_debug.assert_called_with(
1790
+ "USB4 routers found, no need to check DMCUB version"
1791
+ )
1792
+
1793
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1794
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1795
+ @patch("amd_debug.prerequisites.read_file", return_value="0x90001B01")
1796
+ def test_check_dpia_pg_dmcub_dmcub_fw_version_new_enough(
1797
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1798
+ ):
1799
+ """Test check_dpia_pg_dmcub when DMCUB firmware version is new enough"""
1800
+ self.mock_pyudev.list_devices.side_effect = [
1801
+ [], # First call: no USB4
1802
+ [
1803
+ MagicMock(
1804
+ properties={
1805
+ "PCI_CLASS": "30000",
1806
+ "PCI_ID": "1002abcd",
1807
+ "PCI_SLOT_NAME": "0000:01:00.0",
1808
+ },
1809
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1810
+ )
1811
+ ],
1812
+ ]
1813
+ with patch("builtins.open", new_callable=mock_open, read_data="3") as mock_file:
1814
+ handlers = (
1815
+ mock_file.return_value,
1816
+ mock_open(read_data="5").return_value,
1817
+ mock_open(read_data="0").return_value,
1818
+ )
1819
+ mock_open.side_effect = handlers
1820
+ result = self.validator.check_dpia_pg_dmcub()
1821
+ self.assertTrue(result)
1822
+ self.mock_db.record_prereq.assert_not_called()
1823
+
1824
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1825
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1826
+ @patch("amd_debug.prerequisites.read_file", return_value="0x8001B00")
1827
+ def test_check_dpia_pg_dmcub_dmcub_fw_version_too_old(
1828
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1829
+ ):
1830
+ """Test check_dpia_pg_dmcub when DMCUB firmware version is too old"""
1831
+ self.mock_pyudev.list_devices.side_effect = [
1832
+ [], # First call: no USB4
1833
+ [
1834
+ MagicMock(
1835
+ properties={
1836
+ "PCI_CLASS": "30000",
1837
+ "PCI_ID": "1002abcd",
1838
+ "PCI_SLOT_NAME": "0000:01:00.0",
1839
+ },
1840
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1841
+ )
1842
+ ],
1843
+ ]
1844
+ result = self.validator.check_dpia_pg_dmcub()
1845
+ self.assertFalse(result)
1846
+ self.mock_db.record_prereq.assert_called_with(
1847
+ "DMCUB Firmware is outdated", "❌"
1848
+ )
1849
+ self.assertTrue(
1850
+ any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
1851
+ )
1852
+
1853
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1854
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1855
+ @patch(
1856
+ "amd_debug.prerequisites.read_file",
1857
+ side_effect=[
1858
+ "", # sysfs read returns empty, so fallback to debugfs
1859
+ "DMCUB fw: 09001B00\nOther line\n", # debugfs read
1860
+ ],
1861
+ )
1862
+ def test_check_dpia_pg_dmcub_debugfs_version_new_enough(
1863
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1864
+ ):
1865
+ """Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is new enough"""
1866
+ self.mock_pyudev.list_devices.side_effect = [
1867
+ [], # First call: no USB4
1868
+ [
1869
+ MagicMock(
1870
+ properties={
1871
+ "PCI_CLASS": "30000",
1872
+ "PCI_ID": "1002abcd",
1873
+ "PCI_SLOT_NAME": "0",
1874
+ },
1875
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1876
+ )
1877
+ ],
1878
+ ]
1879
+ result = self.validator.check_dpia_pg_dmcub()
1880
+ self.assertTrue(result)
1881
+ self.mock_db.record_prereq.assert_not_called()
1882
+
1883
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1884
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1885
+ @patch(
1886
+ "amd_debug.prerequisites.read_file",
1887
+ side_effect=[
1888
+ "DMCUB fw: 0x08001B00\nOther line\n", # debugfs read
1889
+ ],
1890
+ )
1891
+ def test_check_dpia_pg_dmcub_debugfs_version_too_old(
1892
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1893
+ ):
1894
+ """Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is too old"""
1895
+ self.mock_pyudev.list_devices.side_effect = [
1896
+ [], # First call: no USB4
1897
+ [
1898
+ MagicMock(
1899
+ properties={
1900
+ "PCI_CLASS": "30000",
1901
+ "PCI_ID": "1002abcd",
1902
+ "PCI_SLOT_NAME": "0",
1903
+ },
1904
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1905
+ )
1906
+ ],
1907
+ ]
1908
+ result = self.validator.check_dpia_pg_dmcub()
1909
+ self.assertFalse(result)
1910
+ self.mock_db.record_prereq.assert_called_with(
1911
+ "DMCUB Firmware is outdated", "❌"
1912
+ )
1913
+ self.assertTrue(
1914
+ any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
1915
+ )
1916
+
1917
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=False)
1918
+ def test_check_dpia_pg_dmcub_no_matching_dcn(self, mock_find_ip_version):
1919
+ """Test check_dpia_pg_dmcub when no matching DCN is found"""
1920
+ self.mock_pyudev.list_devices.side_effect = [
1921
+ [], # First call: no USB4
1922
+ [
1923
+ MagicMock(
1924
+ properties={
1925
+ "PCI_CLASS": "30000",
1926
+ "PCI_ID": "1002abcd",
1927
+ "PCI_SLOT_NAME": "0",
1928
+ },
1929
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1930
+ )
1931
+ ],
1932
+ ]
1933
+ result = self.validator.check_dpia_pg_dmcub()
1934
+ self.assertTrue(result)
1935
+ self.mock_db.record_prereq.assert_not_called()